From 1f05fe28234266b1a190d1ce27da829809828d09 Mon Sep 17 00:00:00 2001 From: DmitryLukyanov Date: Mon, 21 Nov 2022 04:25:05 +0400 Subject: [PATCH 1/9] CSHARP-4255: Automatically create Queryable Encryption keys. --- .../Encryption/EncryptedCollectionHelper.cs | 34 ++++++++ .../Encryption/ClientEncryption.cs | 58 +++++++++++++ .../Encryption/LibMongoCryptControllerBase.cs | 3 + .../prose-tests/ClientEncryptionProseTests.cs | 81 ++++++++++++++++++- 4 files changed, 172 insertions(+), 4 deletions(-) diff --git a/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs b/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs index 0e66ad36641..f17b8168b7d 100644 --- a/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs +++ b/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs @@ -76,6 +76,40 @@ public static BsonDocument GetEffectiveEncryptedFields(CollectionNamespace colle } } + public static IEnumerable IterateEmptyKeyIds(CollectionNamespace collectionNamespace, BsonDocument encryptedFields) + { + if (!EncryptedCollectionHelper.TryGetEffectiveEncryptedFields(collectionNamespace, encryptedFields, encryptedFieldsMap: null, out var storedEncryptedFields)) + { + throw new InvalidOperationException("There are no encrypted fields defined for the collection."); + } + + if (storedEncryptedFields.TryGetValue("fields", out var fields) && fields is BsonArray fieldsArray) + { + foreach (var field in fieldsArray) + { + if (field is BsonDocument fieldDocument) + { + if (fieldDocument.TryGetElement("keyId", out var keyId) && keyId.Value == BsonNull.Value) + { + yield return fieldDocument; + } + } + else + { + // If `F` is not a document element, skip it. + continue; + } + } + } + + yield break; + } + + public static void ModifyEndryptedFields(BsonDocument fieldDocument, Guid dataKey) + { + fieldDocument["keyId"] = new BsonBinaryData(dataKey, GuidRepresentation.Standard); + } + public enum HelperCollectionForEncryption { Esc, diff --git a/src/MongoDB.Driver/Encryption/ClientEncryption.cs b/src/MongoDB.Driver/Encryption/ClientEncryption.cs index ce01732ff5b..d9d2de49dcb 100644 --- a/src/MongoDB.Driver/Encryption/ClientEncryption.cs +++ b/src/MongoDB.Driver/Encryption/ClientEncryption.cs @@ -78,6 +78,64 @@ public BsonDocument AddAlternateKeyName(Guid id, string alternateKeyName, Cancel public Task AddAlternateKeyNameAsync(Guid id, string alternateKeyName, CancellationToken cancellationToken = default) => _libMongoCryptController.AddAlternateKeyNameAsync(id, alternateKeyName, cancellationToken); + /// + /// Create encrypted collection. + /// + /// The collection namespace. + /// The create collection options. + /// The kms provider. + /// The datakey options. + /// The cancellation token. + public (IMongoCollection Collection, BsonDocument EncryptedFields) CreateEncryptedCollection(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) + { + var effectiveEncryptedFields = createCollectionOptions?.EncryptedFields?.DeepClone()?.AsBsonDocument; + + foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, effectiveEncryptedFields)) + { + var dataKey = CreateDataKey(kmsProvider, dataKeyOptions, cancellationToken); + EncryptedCollectionHelper.ModifyEndryptedFields(fieldDocument, dataKey); + } + + var database = _libMongoCryptController.KeyVaultClient.GetDatabase(collectionNamespace.DatabaseNamespace.DatabaseName); + + createCollectionOptions.EncryptedFields = effectiveEncryptedFields; + + database.CreateCollection(collectionNamespace.CollectionName, createCollectionOptions, cancellationToken); + + var collection = database.GetCollection(collectionNamespace.CollectionName); + + return (collection, effectiveEncryptedFields); + } + + /// + /// Create encrypted collection. + /// + /// The collection namespace. + /// The create collection options. + /// The kms provider. + /// The datakey options. + /// The cancellation token. + public async Task<(IMongoCollection Collection, BsonDocument EncryptedFields)> CreateEncryptedCollectionAsync(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) + { + var effectiveEncryptedFields = createCollectionOptions?.EncryptedFields?.DeepClone()?.AsBsonDocument; + + foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, effectiveEncryptedFields)) + { + var dataKey = await CreateDataKeyAsync(kmsProvider, dataKeyOptions, cancellationToken).ConfigureAwait(false); + EncryptedCollectionHelper.ModifyEndryptedFields(fieldDocument, dataKey); + } + + var database = _libMongoCryptController.KeyVaultClient.GetDatabase(collectionNamespace.DatabaseNamespace.DatabaseName); + + createCollectionOptions.EncryptedFields = effectiveEncryptedFields; + + await database.CreateCollectionAsync(collectionNamespace.CollectionName, createCollectionOptions, cancellationToken).ConfigureAwait(false); + + var collection = database.GetCollection(collectionNamespace.CollectionName); + + return (collection, effectiveEncryptedFields); + } + /// /// An alias function equivalent to createKey. /// diff --git a/src/MongoDB.Driver/Encryption/LibMongoCryptControllerBase.cs b/src/MongoDB.Driver/Encryption/LibMongoCryptControllerBase.cs index 8c5a0d68515..e39da0e3cbf 100644 --- a/src/MongoDB.Driver/Encryption/LibMongoCryptControllerBase.cs +++ b/src/MongoDB.Driver/Encryption/LibMongoCryptControllerBase.cs @@ -61,6 +61,9 @@ protected LibMongoCryptControllerBase( _tlsOptions = Ensure.IsNotNull(encryptionOptions.TlsOptions, nameof(encryptionOptions.TlsOptions)); } + // public proeprties + public IMongoClient KeyVaultClient => _keyVaultClient; + // protected methods protected void FeedResult(CryptContext context, BsonDocument document) { diff --git a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs index 29ae5dc1028..00998ae2e5b 100644 --- a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs +++ b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs @@ -33,7 +33,6 @@ using MongoDB.Driver.Core.Authentication.External; using MongoDB.Driver.Core.Bindings; using MongoDB.Driver.Core.Clusters; -using MongoDB.Driver.Core.Configuration; using MongoDB.Driver.Core.Events; using MongoDB.Driver.Core.Misc; using MongoDB.Driver.Core.Operations; @@ -84,6 +83,69 @@ public ClientEncryptionProseTests(ITestOutputHelper testOutputHelper) } // public methods + [SkippableTheory] + [ParameterAttributeData] + public void AutomaticDataEncryptionKeys( + [Range(1, 3)] int testCase, + [Values(false, true)] bool async) + { + RequireServer.Check().Supports(Feature.Csfle2); + + var kmsProvider = "local"; + using (var client = ConfigureClient()) + using (var clientEncryption = ConfigureClientEncryption(client, kmsProviderFilter: kmsProvider)) + { + var encryptedFields = BsonDocument.Parse($@" + {{ + fields: + [ + {{ + path: ""ssn"", + bsonType: ""string"", + keyId: null + }} + ] + }}"); + + DropCollection(__collCollectionNamespace, encryptedFields); + + RunTestCase(testCase); + + void RunTestCase(int testCase) + { + switch (testCase) + { + case 1: // Case 1: Simple Creation and Validation + { + var collection = CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, encryptedFields, kmsProvider, async).Collection; + + var exception = Record.Exception(() => Insert(collection, async, new BsonDocument("ssn", "123-45-6789"))); + exception.Should().BeOfType>().Which.Message.Should().Contain("Document failed validation"); + } + break; + case 2: // Case 2: Missing ``encryptedFields`` + { + var exception = Record.Exception(() => CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, encryptedFields: null, kmsProvider, async).Collection); + + exception.Should().BeOfType().Which.Message.Should().Contain("There are no encrypted fields defined for the collection.") ; + } + break; + case 3: // Case 3: Invalid ``keyId`` + { + var effectiveEncryptedFields = encryptedFields.DeepClone(); + effectiveEncryptedFields["fields"].AsBsonArray[0].AsBsonDocument["keyId"] = true; + var collection = CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, encryptedFields, kmsProvider, async).Collection; + + var exception = Record.Exception(() => Insert(collection, async, new BsonDocument("ssn", "123-45-6789"))); + exception.Should().BeOfType>().Which.Message.Should().Contain("Document failed validation"); + } + break; + default: throw new Exception($"Unexpected test case {testCase}."); + } + } + } + } + [SkippableTheory] [ParameterAttributeData] public void BsonSizeLimitAndBatchSizeSplittingTest( @@ -1025,6 +1087,7 @@ void RunTestCase(IMongoCollection decryptionEventsCollection, int reply["cursor"]["firstBatch"].AsBsonArray.Single()["encrypted"].AsBsonBinaryData.SubType.Should().Be(BsonBinarySubType.Encrypted); } break; + default: throw new Exception($"Unexpected test case {testCase}."); } } @@ -1873,7 +1936,7 @@ public void ViewAreProhibitedTest([Values(false, true)] bool async) using (var client = ConfigureClient(false)) using (var clientEncrypted = ConfigureClientEncrypted(kmsProviderFilter: "local")) { - DropView(viewName); + DropCollection(viewName); client .GetDatabase(viewName.DatabaseNamespace.DatabaseName) .CreateView( @@ -2201,6 +2264,16 @@ private void CreateCollection(IMongoClient client, CollectionNamespace collectio }); } + private (IMongoCollection Collection, BsonDocument EncryptedFields) CreateEncryptedCollection(IMongoClient client, ClientEncryption clientEncryption, CollectionNamespace collectionNamespace, BsonDocument encryptedFields, string kmsProvider, bool async) + { + var createCollectionOptions = new CreateCollectionOptions { EncryptedFields = encryptedFields }; + var datakeyOptions = CreateDataKeyOptions(kmsProvider); + + return async + ? clientEncryption.CreateEncryptedCollectionAsync(collectionNamespace, createCollectionOptions, kmsProvider, datakeyOptions, cancellationToken: default).GetAwaiter().GetResult() + : clientEncryption.CreateEncryptedCollection(collectionNamespace, createCollectionOptions, kmsProvider, datakeyOptions, cancellationToken: default); + } + private Guid CreateDataKey( ClientEncryption clientEncryption, string kmsProvider, @@ -2351,9 +2424,9 @@ private MongoClientSettings CreateMongoClientSettings( return mongoClientSettings; } - private void DropView(CollectionNamespace viewNamespace) + private void DropCollection(CollectionNamespace viewNamespace, BsonDocument encryptedFields = null) { - var operation = new DropCollectionOperation(viewNamespace, CoreTestConfiguration.MessageEncoderSettings); + var operation = DropCollectionOperation.CreateEncryptedDropCollectionOperationIfConfigured(viewNamespace, encryptedFields, CoreTestConfiguration.MessageEncoderSettings, configureDropCollectionConfigurator: null); using (var session = CoreTestConfiguration.StartSession(_cluster)) using (var binding = new WritableServerBinding(_cluster, session.Fork())) using (var bindingHandle = new ReadWriteBindingHandle(binding)) From 71e171e87ffaac150c59e7574bf2d07fba0dcba4 Mon Sep 17 00:00:00 2001 From: DmitryLukyanov Date: Wed, 23 Nov 2022 02:42:17 +0400 Subject: [PATCH 2/9] Test fix. --- .../prose-tests/ClientEncryptionProseTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs index 00998ae2e5b..73de92c463e 100644 --- a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs +++ b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs @@ -89,7 +89,7 @@ public void AutomaticDataEncryptionKeys( [Range(1, 3)] int testCase, [Values(false, true)] bool async) { - RequireServer.Check().Supports(Feature.Csfle2); + RequireServer.Check().Supports(Feature.Csfle2).ClusterTypes(ClusterType.ReplicaSet, ClusterType.Sharded, ClusterType.LoadBalanced); var kmsProvider = "local"; using (var client = ConfigureClient()) From 93183487ee9009f9b41d2f634e17964408e54722 Mon Sep 17 00:00:00 2001 From: DmitryLukyanov Date: Fri, 25 Nov 2022 04:56:44 +0400 Subject: [PATCH 3/9] Code review, --- .../Core/Encryption/EncryptedCollectionHelper.cs | 16 +++------------- .../prose-tests/ClientEncryptionProseTests.cs | 8 +++----- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs b/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs index f17b8168b7d..55e4d8f420e 100644 --- a/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs +++ b/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs @@ -85,24 +85,14 @@ public static IEnumerable IterateEmptyKeyIds(CollectionNamespace c if (storedEncryptedFields.TryGetValue("fields", out var fields) && fields is BsonArray fieldsArray) { - foreach (var field in fieldsArray) + foreach (var field in fieldsArray.OfType()) // If `F` is not a document element, skip it. { - if (field is BsonDocument fieldDocument) + if (field.TryGetElement("keyId", out var keyId) && keyId.Value == BsonNull.Value) { - if (fieldDocument.TryGetElement("keyId", out var keyId) && keyId.Value == BsonNull.Value) - { - yield return fieldDocument; - } - } - else - { - // If `F` is not a document element, skip it. - continue; + yield return field; } } } - - yield break; } public static void ModifyEndryptedFields(BsonDocument fieldDocument, Guid dataKey) diff --git a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs index 73de92c463e..66b941815b7 100644 --- a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs +++ b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs @@ -133,11 +133,9 @@ void RunTestCase(int testCase) case 3: // Case 3: Invalid ``keyId`` { var effectiveEncryptedFields = encryptedFields.DeepClone(); - effectiveEncryptedFields["fields"].AsBsonArray[0].AsBsonDocument["keyId"] = true; - var collection = CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, encryptedFields, kmsProvider, async).Collection; - - var exception = Record.Exception(() => Insert(collection, async, new BsonDocument("ssn", "123-45-6789"))); - exception.Should().BeOfType>().Which.Message.Should().Contain("Document failed validation"); + effectiveEncryptedFields["fields"].AsBsonArray[0].AsBsonDocument["keyId"] = false; + var exception = Record.Exception(() => CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, effectiveEncryptedFields.AsBsonDocument, kmsProvider, async)); + exception.Should().BeOfType().Which.Message.Should().Contain("BSON field 'create.encryptedFields.fields.keyId' is the wrong type 'bool', expected type 'binData'"); } break; default: throw new Exception($"Unexpected test case {testCase}."); From 2b8e02a37f3bc21cfe2b0b48fb44bd241acdba5b Mon Sep 17 00:00:00 2001 From: DmitryLukyanov Date: Fri, 25 Nov 2022 04:59:55 +0400 Subject: [PATCH 4/9] Code review --- .../prose-tests/ClientEncryptionProseTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs index 66b941815b7..b43110925ea 100644 --- a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs +++ b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs @@ -2422,9 +2422,9 @@ private MongoClientSettings CreateMongoClientSettings( return mongoClientSettings; } - private void DropCollection(CollectionNamespace viewNamespace, BsonDocument encryptedFields = null) + private void DropCollection(CollectionNamespace collectionNamespace, BsonDocument encryptedFields = null) { - var operation = DropCollectionOperation.CreateEncryptedDropCollectionOperationIfConfigured(viewNamespace, encryptedFields, CoreTestConfiguration.MessageEncoderSettings, configureDropCollectionConfigurator: null); + var operation = DropCollectionOperation.CreateEncryptedDropCollectionOperationIfConfigured(collectionNamespace, encryptedFields, CoreTestConfiguration.MessageEncoderSettings, configureDropCollectionConfigurator: null); using (var session = CoreTestConfiguration.StartSession(_cluster)) using (var binding = new WritableServerBinding(_cluster, session.Fork())) using (var bindingHandle = new ReadWriteBindingHandle(binding)) From 8b8705c9a6266c8d996041d093dc5362ca9a2196 Mon Sep 17 00:00:00 2001 From: DmitryLukyanov Date: Thu, 8 Dec 2022 03:05:58 +0400 Subject: [PATCH 5/9] Fix typo. --- .../Core/Encryption/EncryptedCollectionHelper.cs | 2 +- src/MongoDB.Driver/Encryption/ClientEncryption.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs b/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs index 55e4d8f420e..7fc13b4a363 100644 --- a/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs +++ b/src/MongoDB.Driver.Core/Core/Encryption/EncryptedCollectionHelper.cs @@ -95,7 +95,7 @@ public static IEnumerable IterateEmptyKeyIds(CollectionNamespace c } } - public static void ModifyEndryptedFields(BsonDocument fieldDocument, Guid dataKey) + public static void ModifyEncryptedFields(BsonDocument fieldDocument, Guid dataKey) { fieldDocument["keyId"] = new BsonBinaryData(dataKey, GuidRepresentation.Standard); } diff --git a/src/MongoDB.Driver/Encryption/ClientEncryption.cs b/src/MongoDB.Driver/Encryption/ClientEncryption.cs index d9d2de49dcb..19e97694097 100644 --- a/src/MongoDB.Driver/Encryption/ClientEncryption.cs +++ b/src/MongoDB.Driver/Encryption/ClientEncryption.cs @@ -93,7 +93,7 @@ public Task AddAlternateKeyNameAsync(Guid id, string alternateKeyN foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, effectiveEncryptedFields)) { var dataKey = CreateDataKey(kmsProvider, dataKeyOptions, cancellationToken); - EncryptedCollectionHelper.ModifyEndryptedFields(fieldDocument, dataKey); + EncryptedCollectionHelper.ModifyEncryptedFields(fieldDocument, dataKey); } var database = _libMongoCryptController.KeyVaultClient.GetDatabase(collectionNamespace.DatabaseNamespace.DatabaseName); @@ -122,7 +122,7 @@ public Task AddAlternateKeyNameAsync(Guid id, string alternateKeyN foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, effectiveEncryptedFields)) { var dataKey = await CreateDataKeyAsync(kmsProvider, dataKeyOptions, cancellationToken).ConfigureAwait(false); - EncryptedCollectionHelper.ModifyEndryptedFields(fieldDocument, dataKey); + EncryptedCollectionHelper.ModifyEncryptedFields(fieldDocument, dataKey); } var database = _libMongoCryptController.KeyVaultClient.GetDatabase(collectionNamespace.DatabaseNamespace.DatabaseName); From c0b7f6a01c00494fe1c13ee7f8e22b8f75a5419f Mon Sep 17 00:00:00 2001 From: DmitryLukyanov Date: Thu, 8 Dec 2022 03:07:02 +0400 Subject: [PATCH 6/9] Fix rewrapProseTest --- .../prose-tests/ClientEncryptionProseTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs index b43110925ea..5014bf461c9 100644 --- a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs +++ b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs @@ -1880,6 +1880,8 @@ HttpClientWrapperWithModifiedRequest CreateHttpClientWrapperWithModifiedRequest( } } + [SkippableTheory] + [ParameterAttributeData] public void RewrapTest( [Values("local", "aws", "azure", "gcp", "kmip")] string srcProvider, [Values("local", "aws", "azure", "gcp", "kmip")] string dstProvider, From c0e4ab16a610dd322f4f8503cefa03a1b3aa7e57 Mon Sep 17 00:00:00 2001 From: DmitryLukyanov Date: Thu, 8 Dec 2022 03:28:46 +0400 Subject: [PATCH 7/9] Removed return type. --- .../Encryption/ClientEncryption.cs | 30 +++++++------------ .../prose-tests/ClientEncryptionProseTests.cs | 19 ++++++++---- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/MongoDB.Driver/Encryption/ClientEncryption.cs b/src/MongoDB.Driver/Encryption/ClientEncryption.cs index 19e97694097..2e800f9b967 100644 --- a/src/MongoDB.Driver/Encryption/ClientEncryption.cs +++ b/src/MongoDB.Driver/Encryption/ClientEncryption.cs @@ -86,11 +86,12 @@ public Task AddAlternateKeyNameAsync(Guid id, string alternateKeyN /// The kms provider. /// The datakey options. /// The cancellation token. - public (IMongoCollection Collection, BsonDocument EncryptedFields) CreateEncryptedCollection(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) + /// + /// if EncryptionFields contains a keyId with a null value, a data key will be automatically generated and assigned to keyId value. + /// + public void CreateEncryptedCollection(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) { - var effectiveEncryptedFields = createCollectionOptions?.EncryptedFields?.DeepClone()?.AsBsonDocument; - - foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, effectiveEncryptedFields)) + foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, createCollectionOptions.EncryptedFields)) { var dataKey = CreateDataKey(kmsProvider, dataKeyOptions, cancellationToken); EncryptedCollectionHelper.ModifyEncryptedFields(fieldDocument, dataKey); @@ -98,13 +99,7 @@ public Task AddAlternateKeyNameAsync(Guid id, string alternateKeyN var database = _libMongoCryptController.KeyVaultClient.GetDatabase(collectionNamespace.DatabaseNamespace.DatabaseName); - createCollectionOptions.EncryptedFields = effectiveEncryptedFields; - database.CreateCollection(collectionNamespace.CollectionName, createCollectionOptions, cancellationToken); - - var collection = database.GetCollection(collectionNamespace.CollectionName); - - return (collection, effectiveEncryptedFields); } /// @@ -115,11 +110,12 @@ public Task AddAlternateKeyNameAsync(Guid id, string alternateKeyN /// The kms provider. /// The datakey options. /// The cancellation token. - public async Task<(IMongoCollection Collection, BsonDocument EncryptedFields)> CreateEncryptedCollectionAsync(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) + /// + /// if EncryptionFields contains a keyId with a null value, a data key will be automatically generated and assigned to keyId value. + /// + public async Task CreateEncryptedCollectionAsync(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) { - var effectiveEncryptedFields = createCollectionOptions?.EncryptedFields?.DeepClone()?.AsBsonDocument; - - foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, effectiveEncryptedFields)) + foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, createCollectionOptions.EncryptedFields)) { var dataKey = await CreateDataKeyAsync(kmsProvider, dataKeyOptions, cancellationToken).ConfigureAwait(false); EncryptedCollectionHelper.ModifyEncryptedFields(fieldDocument, dataKey); @@ -127,13 +123,7 @@ public Task AddAlternateKeyNameAsync(Guid id, string alternateKeyN var database = _libMongoCryptController.KeyVaultClient.GetDatabase(collectionNamespace.DatabaseNamespace.DatabaseName); - createCollectionOptions.EncryptedFields = effectiveEncryptedFields; - await database.CreateCollectionAsync(collectionNamespace.CollectionName, createCollectionOptions, cancellationToken).ConfigureAwait(false); - - var collection = database.GetCollection(collectionNamespace.CollectionName); - - return (collection, effectiveEncryptedFields); } /// diff --git a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs index 5014bf461c9..0b56b9a92e7 100644 --- a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs +++ b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs @@ -117,7 +117,7 @@ void RunTestCase(int testCase) { case 1: // Case 1: Simple Creation and Validation { - var collection = CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, encryptedFields, kmsProvider, async).Collection; + var collection = CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, encryptedFields, kmsProvider, async); var exception = Record.Exception(() => Insert(collection, async, new BsonDocument("ssn", "123-45-6789"))); exception.Should().BeOfType>().Which.Message.Should().Contain("Document failed validation"); @@ -125,7 +125,7 @@ void RunTestCase(int testCase) break; case 2: // Case 2: Missing ``encryptedFields`` { - var exception = Record.Exception(() => CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, encryptedFields: null, kmsProvider, async).Collection); + var exception = Record.Exception(() => CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, encryptedFields: null, kmsProvider, async)); exception.Should().BeOfType().Which.Message.Should().Contain("There are no encrypted fields defined for the collection.") ; } @@ -2264,14 +2264,21 @@ private void CreateCollection(IMongoClient client, CollectionNamespace collectio }); } - private (IMongoCollection Collection, BsonDocument EncryptedFields) CreateEncryptedCollection(IMongoClient client, ClientEncryption clientEncryption, CollectionNamespace collectionNamespace, BsonDocument encryptedFields, string kmsProvider, bool async) + private IMongoCollection CreateEncryptedCollection(IMongoClient client, ClientEncryption clientEncryption, CollectionNamespace collectionNamespace, BsonDocument encryptedFields, string kmsProvider, bool async) { var createCollectionOptions = new CreateCollectionOptions { EncryptedFields = encryptedFields }; var datakeyOptions = CreateDataKeyOptions(kmsProvider); - return async - ? clientEncryption.CreateEncryptedCollectionAsync(collectionNamespace, createCollectionOptions, kmsProvider, datakeyOptions, cancellationToken: default).GetAwaiter().GetResult() - : clientEncryption.CreateEncryptedCollection(collectionNamespace, createCollectionOptions, kmsProvider, datakeyOptions, cancellationToken: default); + if (async) + { + clientEncryption.CreateEncryptedCollectionAsync(collectionNamespace, createCollectionOptions, kmsProvider, datakeyOptions, cancellationToken: default).GetAwaiter().GetResult(); + } + else + { + clientEncryption.CreateEncryptedCollection(collectionNamespace, createCollectionOptions, kmsProvider, datakeyOptions, cancellationToken: default); + } + + return client.GetDatabase(collectionNamespace.DatabaseNamespace.DatabaseName).GetCollection(collectionNamespace.CollectionName); } private Guid CreateDataKey( From 6013fc2d10a86eca4a23dc915fc5797722904c0d Mon Sep 17 00:00:00 2001 From: DmitryLukyanov Date: Wed, 14 Dec 2022 02:45:42 +0400 Subject: [PATCH 8/9] Add positive scenario. --- .../Encryption/ClientEncryption.cs | 9 +++++++++ .../prose-tests/ClientEncryptionProseTests.cs | 18 ++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/MongoDB.Driver/Encryption/ClientEncryption.cs b/src/MongoDB.Driver/Encryption/ClientEncryption.cs index 2e800f9b967..684c7f2507f 100644 --- a/src/MongoDB.Driver/Encryption/ClientEncryption.cs +++ b/src/MongoDB.Driver/Encryption/ClientEncryption.cs @@ -20,6 +20,7 @@ using MongoDB.Bson; using MongoDB.Driver.Core.Clusters; using MongoDB.Driver.Core.Configuration; +using MongoDB.Driver.Core.Misc; using MongoDB.Libmongocrypt; namespace MongoDB.Driver.Encryption @@ -91,6 +92,10 @@ public Task AddAlternateKeyNameAsync(Guid id, string alternateKeyN /// public void CreateEncryptedCollection(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) { + Ensure.IsNotNull(createCollectionOptions, nameof(createCollectionOptions)); + Ensure.IsNotNull(dataKeyOptions, nameof(dataKeyOptions)); + Ensure.IsNotNull(kmsProvider, nameof(kmsProvider)); + foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, createCollectionOptions.EncryptedFields)) { var dataKey = CreateDataKey(kmsProvider, dataKeyOptions, cancellationToken); @@ -115,6 +120,10 @@ public void CreateEncryptedCollection(CollectionNamespace collectio /// public async Task CreateEncryptedCollectionAsync(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) { + Ensure.IsNotNull(createCollectionOptions, nameof(createCollectionOptions)); + Ensure.IsNotNull(dataKeyOptions, nameof(dataKeyOptions)); + Ensure.IsNotNull(kmsProvider, nameof(kmsProvider)); + foreach (var fieldDocument in EncryptedCollectionHelper.IterateEmptyKeyIds(collectionNamespace, createCollectionOptions.EncryptedFields)) { var dataKey = await CreateDataKeyAsync(kmsProvider, dataKeyOptions, cancellationToken).ConfigureAwait(false); diff --git a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs index 0b56b9a92e7..272079b6dbb 100644 --- a/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs +++ b/tests/MongoDB.Driver.Tests/Specifications/client-side-encryption/prose-tests/ClientEncryptionProseTests.cs @@ -85,8 +85,8 @@ public ClientEncryptionProseTests(ITestOutputHelper testOutputHelper) // public methods [SkippableTheory] [ParameterAttributeData] - public void AutomaticDataEncryptionKeys( - [Range(1, 3)] int testCase, + public void AutomaticDataEncryptionKeysTest( + [Range(1, 4)] int testCase, [Values(false, true)] bool async) { RequireServer.Check().Supports(Feature.Csfle2).ClusterTypes(ClusterType.ReplicaSet, ClusterType.Sharded, ClusterType.LoadBalanced); @@ -138,6 +138,15 @@ void RunTestCase(int testCase) exception.Should().BeOfType().Which.Message.Should().Contain("BSON field 'create.encryptedFields.fields.keyId' is the wrong type 'bool', expected type 'binData'"); } break; + case 4: // Case 4: Insert encrypted value + { + var createCollectionOptions = new CreateCollectionOptions { EncryptedFields = encryptedFields }; + var collection = CreateEncryptedCollection(client, clientEncryption, __collCollectionNamespace, createCollectionOptions, kmsProvider, async); + var dataKey = createCollectionOptions.EncryptedFields["fields"].AsBsonArray[0].AsBsonDocument["keyId"].AsGuid; // get generated datakey + var encryptedValue = ExplicitEncrypt(clientEncryption, new EncryptOptions(algorithm: EncryptionAlgorithm.Unindexed, keyId: dataKey), "123-45-6789", async); // use explicit encryption to encrypt data before inserting + Insert(collection, async, new BsonDocument("ssn", encryptedValue)); + } + break; default: throw new Exception($"Unexpected test case {testCase}."); } } @@ -2267,6 +2276,11 @@ private void CreateCollection(IMongoClient client, CollectionNamespace collectio private IMongoCollection CreateEncryptedCollection(IMongoClient client, ClientEncryption clientEncryption, CollectionNamespace collectionNamespace, BsonDocument encryptedFields, string kmsProvider, bool async) { var createCollectionOptions = new CreateCollectionOptions { EncryptedFields = encryptedFields }; + return CreateEncryptedCollection(client, clientEncryption, collectionNamespace, createCollectionOptions, kmsProvider, async); + } + + private IMongoCollection CreateEncryptedCollection(IMongoClient client, ClientEncryption clientEncryption, CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, bool async) + { var datakeyOptions = CreateDataKeyOptions(kmsProvider); if (async) From 4e6d6b958f10cd0d27c6e3d3861f56d7dab55936 Mon Sep 17 00:00:00 2001 From: DmitryLukyanov Date: Wed, 14 Dec 2022 21:57:15 +0400 Subject: [PATCH 9/9] Code review. --- src/MongoDB.Driver/Encryption/ClientEncryption.cs | 2 ++ src/MongoDB.Driver/Encryption/LibMongoCryptControllerBase.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/MongoDB.Driver/Encryption/ClientEncryption.cs b/src/MongoDB.Driver/Encryption/ClientEncryption.cs index 684c7f2507f..7dfadcb36b5 100644 --- a/src/MongoDB.Driver/Encryption/ClientEncryption.cs +++ b/src/MongoDB.Driver/Encryption/ClientEncryption.cs @@ -92,6 +92,7 @@ public Task AddAlternateKeyNameAsync(Guid id, string alternateKeyN /// public void CreateEncryptedCollection(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) { + Ensure.IsNotNull(collectionNamespace, nameof(collectionNamespace)); Ensure.IsNotNull(createCollectionOptions, nameof(createCollectionOptions)); Ensure.IsNotNull(dataKeyOptions, nameof(dataKeyOptions)); Ensure.IsNotNull(kmsProvider, nameof(kmsProvider)); @@ -120,6 +121,7 @@ public void CreateEncryptedCollection(CollectionNamespace collectio /// public async Task CreateEncryptedCollectionAsync(CollectionNamespace collectionNamespace, CreateCollectionOptions createCollectionOptions, string kmsProvider, DataKeyOptions dataKeyOptions, CancellationToken cancellationToken = default) { + Ensure.IsNotNull(collectionNamespace, nameof(collectionNamespace)); Ensure.IsNotNull(createCollectionOptions, nameof(createCollectionOptions)); Ensure.IsNotNull(dataKeyOptions, nameof(dataKeyOptions)); Ensure.IsNotNull(kmsProvider, nameof(kmsProvider)); diff --git a/src/MongoDB.Driver/Encryption/LibMongoCryptControllerBase.cs b/src/MongoDB.Driver/Encryption/LibMongoCryptControllerBase.cs index e39da0e3cbf..e7464d3d8ee 100644 --- a/src/MongoDB.Driver/Encryption/LibMongoCryptControllerBase.cs +++ b/src/MongoDB.Driver/Encryption/LibMongoCryptControllerBase.cs @@ -61,7 +61,7 @@ protected LibMongoCryptControllerBase( _tlsOptions = Ensure.IsNotNull(encryptionOptions.TlsOptions, nameof(encryptionOptions.TlsOptions)); } - // public proeprties + // public properties public IMongoClient KeyVaultClient => _keyVaultClient; // protected methods