Skip to content

Commit 405cb7c

Browse files
rstamDmitryLukyanov
authored andcommitted
CSHARP-2431: Support Client-side Field Level Encryption.
1 parent 85c5f48 commit 405cb7c

File tree

131 files changed

+29776
-193
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

131 files changed

+29776
-193
lines changed

src/MongoDB.Bson/IO/ElementAppendingBsonWriter.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,30 @@ public override void WriteEndDocument()
7474
base.WriteEndDocument();
7575
}
7676

77+
public override void WriteRawBsonDocument(IByteBuffer slice)
78+
{
79+
WriteStartDocument();
80+
81+
if (Wrapped is BsonBinaryWriter binaryWriter)
82+
{
83+
// just copy the bytes (without the length and terminating null)
84+
var lengthBytes = new byte[4];
85+
slice.GetBytes(0, lengthBytes, 0, 4);
86+
var length = BitConverter.ToInt32(lengthBytes, 0);
87+
using (var elements = slice.GetSlice(4, length - 5))
88+
{
89+
var stream = binaryWriter.BsonStream;
90+
stream.WriteSlice(elements);
91+
}
92+
}
93+
else
94+
{
95+
throw new NotSupportedException("WriteRawBsonDocument supports only BsonBinaryWriter.");
96+
}
97+
98+
WriteEndDocument();
99+
}
100+
77101
/// <inheritdoc />
78102
public override void WriteStartDocument()
79103
{

src/MongoDB.Bson/IO/JsonReader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1195,7 +1195,7 @@ private BsonValue ParseDateTimeExtendedJson()
11951195
}
11961196
else if (valueToken.Type == JsonTokenType.BeginObject)
11971197
{
1198-
VerifyToken("$numberLong");
1198+
VerifyString("$numberLong");
11991199
VerifyToken(":");
12001200
var millisecondsSinceEpochToken = PopToken();
12011201
if (millisecondsSinceEpochToken.Type == JsonTokenType.String)

src/MongoDB.Bson/ObjectModel/BsonBinarySubType.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ public enum BsonBinarySubType
5151
/// </summary>
5252
MD5 = 0x05,
5353
/// <summary>
54+
/// Encrypted binary data.
55+
/// </summary>
56+
Encrypted = 0x06,
57+
/// <summary>
5458
/// User defined binary data.
5559
/// </summary>
5660
UserDefined = 0x80

src/MongoDB.Driver.Core/Core/Clusters/Cluster.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
using MongoDB.Driver.Core.Events;
2727
using MongoDB.Driver.Core.Misc;
2828
using MongoDB.Driver.Core.Servers;
29+
using MongoDB.Libmongocrypt;
2930

3031
namespace MongoDB.Driver.Core.Clusters
3132
{
@@ -62,6 +63,7 @@ internal abstract class Cluster : ICluster
6263
// fields
6364
private readonly IClusterClock _clusterClock = new ClusterClock();
6465
private readonly ClusterId _clusterId;
66+
private CryptClient _cryptClient = null;
6567
private ClusterDescription _description;
6668
private TaskCompletionSource<bool> _descriptionChangedTaskCompletionSource;
6769
private readonly object _descriptionLock = new object();
@@ -109,6 +111,11 @@ public ClusterId ClusterId
109111
get { return _clusterId; }
110112
}
111113

114+
public CryptClient CryptClient
115+
{
116+
get { return _cryptClient; }
117+
}
118+
112119
public ClusterDescription Description
113120
{
114121
get
@@ -188,7 +195,13 @@ private void ExitServerSelectionWaitQueue()
188195
public virtual void Initialize()
189196
{
190197
ThrowIfDisposed();
191-
_state.TryChange(State.Initial, State.Open);
198+
if (_state.TryChange(State.Initial, State.Open))
199+
{
200+
if (_settings.KmsProviders != null || _settings.SchemaMap != null)
201+
{
202+
_cryptClient = CryptClientCreator.CreateCryptClient(_settings.KmsProviders, _settings.SchemaMap);
203+
}
204+
}
192205
}
193206

194207
private void RapidHeartbeatTimerCallback(object args)
@@ -350,7 +363,7 @@ private async Task WaitForDescriptionChangedAsync(IServerSelector selector, Clus
350363
{
351364
using (var helper = new WaitForDescriptionChangedHelper(this, selector, description, descriptionChangedTask, timeout, cancellationToken))
352365
{
353-
var completedTask = await Task.WhenAny(helper.Tasks).ConfigureAwait(false);
366+
var completedTask = await Task.WhenAny(helper.Tasks).ConfigureAwait(false);
354367
helper.HandleCompletedTask(completedTask);
355368
}
356369
}
@@ -528,7 +541,7 @@ private sealed class WaitForDescriptionChangedHelper : IDisposable
528541
private readonly CancellationTokenSource _timeoutCancellationTokenSource;
529542
private readonly Task _timeoutTask;
530543

531-
public WaitForDescriptionChangedHelper(Cluster cluster, IServerSelector selector, ClusterDescription description, Task descriptionChangedTask , TimeSpan timeout, CancellationToken cancellationToken)
544+
public WaitForDescriptionChangedHelper(Cluster cluster, IServerSelector selector, ClusterDescription description, Task descriptionChangedTask, TimeSpan timeout, CancellationToken cancellationToken)
532545
{
533546
_cluster = cluster;
534547
_description = description;
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/* Copyright 2019-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using MongoDB.Bson;
20+
using MongoDB.Bson.IO;
21+
using MongoDB.Driver.Core.Misc;
22+
using MongoDB.Libmongocrypt;
23+
24+
namespace MongoDB.Driver.Core.Clusters
25+
{
26+
/// <summary>
27+
/// Represents a creator for CryptClient.
28+
/// </summary>
29+
public sealed class CryptClientCreator
30+
{
31+
#region static
32+
#pragma warning disable 3002
33+
/// <summary>
34+
/// Create a CryptClient instance.
35+
/// </summary>
36+
/// <param name="kmsProviders">The kms providers.</param>
37+
/// <param name="schemaMap">The schema map.</param>
38+
/// <returns>The CryptClient instance.</returns>
39+
public static CryptClient CreateCryptClient(
40+
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>> kmsProviders,
41+
IReadOnlyDictionary<string, BsonDocument> schemaMap)
42+
{
43+
var helper = new CryptClientCreator(kmsProviders, schemaMap);
44+
var cryptOptions = helper.CreateCryptOptions();
45+
return helper.CreateCryptClient(cryptOptions);
46+
}
47+
#pragma warning restore
48+
#endregion
49+
50+
private readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>> _kmsProviders;
51+
private readonly IReadOnlyDictionary<string, BsonDocument> _schemaMap;
52+
53+
private CryptClientCreator(
54+
IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>> kmsProviders,
55+
IReadOnlyDictionary<string, BsonDocument> schemaMap)
56+
{
57+
_kmsProviders = Ensure.IsNotNull(kmsProviders, nameof(kmsProviders));
58+
_schemaMap = schemaMap;
59+
}
60+
61+
private CryptClient CreateCryptClient(CryptOptions options)
62+
{
63+
return CryptClientFactory.Create(options);
64+
}
65+
66+
private CryptOptions CreateCryptOptions()
67+
{
68+
Dictionary<KmsType, IKmsCredentials> kmsProvidersMap = null;
69+
if (_kmsProviders != null && _kmsProviders.Count > 0)
70+
{
71+
kmsProvidersMap = new Dictionary<KmsType, IKmsCredentials>();
72+
if (_kmsProviders.TryGetValue("aws", out var awsProvider))
73+
{
74+
if (awsProvider.TryGetValue("accessKeyId", out var accessKeyId) &&
75+
awsProvider.TryGetValue("secretAccessKey", out var secretAccessKey))
76+
{
77+
kmsProvidersMap.Add(KmsType.Aws, new AwsKmsCredentials((string)secretAccessKey, (string)accessKeyId));
78+
}
79+
}
80+
if (_kmsProviders.TryGetValue("local", out var localProvider))
81+
{
82+
if (localProvider.TryGetValue("key", out var keyObject) && keyObject is byte[] key)
83+
{
84+
kmsProvidersMap.Add(KmsType.Local, new LocalKmsCredentials(key));
85+
}
86+
}
87+
}
88+
else
89+
{
90+
throw new ArgumentException("At least one kms provider must be specified");
91+
}
92+
93+
byte[] schemaBytes = null;
94+
if (_schemaMap != null)
95+
{
96+
var schemaMapElements = _schemaMap.Select(c => new BsonElement(c.Key, c.Value));
97+
var schemaDocument = new BsonDocument(schemaMapElements);
98+
var writerSettings = new BsonBinaryWriterSettings { GuidRepresentation = GuidRepresentation.Unspecified };
99+
schemaBytes = schemaDocument.ToBson(writerSettings: writerSettings);
100+
}
101+
102+
return new CryptOptions(kmsProvidersMap, schemaBytes);
103+
}
104+
}
105+
}

src/MongoDB.Driver.Core/Core/Clusters/ICluster.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using MongoDB.Driver.Core.Clusters.ServerSelectors;
2121
using MongoDB.Driver.Core.Configuration;
2222
using MongoDB.Driver.Core.Servers;
23+
using MongoDB.Libmongocrypt;
2324

2425
namespace MongoDB.Driver.Core.Clusters
2526
{
@@ -66,6 +67,14 @@ public interface ICluster : IDisposable
6667
/// <returns>A core server session.</returns>
6768
ICoreServerSession AcquireServerSession();
6869

70+
/// <summary>
71+
/// Gets the crypt client.
72+
/// </summary>
73+
/// <returns>A crypt client.</returns>
74+
#pragma warning disable CS3003
75+
CryptClient CryptClient { get; }
76+
#pragma warning restore
77+
6978
/// <summary>
7079
/// Initializes the cluster.
7180
/// </summary>

src/MongoDB.Driver.Core/Core/Configuration/ClusterSettings.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using System.Collections.Generic;
1818
using System.Linq;
1919
using System.Net;
20+
using MongoDB.Bson;
2021
using MongoDB.Driver.Core.Clusters;
2122
using MongoDB.Driver.Core.Clusters.ServerSelectors;
2223
using MongoDB.Driver.Core.Misc;
@@ -36,8 +37,10 @@ public class ClusterSettings
3637
// fields
3738
private readonly ClusterConnectionMode _connectionMode;
3839
private readonly IReadOnlyList<EndPoint> _endPoints;
40+
private readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>> _kmsProviders;
3941
private readonly int _maxServerSelectionWaitQueueSize;
4042
private readonly string _replicaSetName;
43+
private readonly IReadOnlyDictionary<string, BsonDocument> _schemaMap;
4144
private readonly ConnectionStringScheme _scheme;
4245
private readonly TimeSpan _serverSelectionTimeout;
4346
private readonly IServerSelector _preServerSelector;
@@ -49,30 +52,36 @@ public class ClusterSettings
4952
/// </summary>
5053
/// <param name="connectionMode">The connection mode.</param>
5154
/// <param name="endPoints">The end points.</param>
55+
/// <param name="kmsProviders">The kms providers.</param>
5256
/// <param name="maxServerSelectionWaitQueueSize">Maximum size of the server selection wait queue.</param>
5357
/// <param name="replicaSetName">Name of the replica set.</param>
5458
/// <param name="serverSelectionTimeout">The server selection timeout.</param>
5559
/// <param name="preServerSelector">The pre server selector.</param>
5660
/// <param name="postServerSelector">The post server selector.</param>
61+
/// <param name="schemaMap">The schema map.</param>
5762
/// <param name="scheme">The connection string scheme.</param>
5863
public ClusterSettings(
5964
Optional<ClusterConnectionMode> connectionMode = default(Optional<ClusterConnectionMode>),
6065
Optional<IEnumerable<EndPoint>> endPoints = default(Optional<IEnumerable<EndPoint>>),
66+
Optional<IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>>> kmsProviders = default(Optional<IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>>>),
6167
Optional<int> maxServerSelectionWaitQueueSize = default(Optional<int>),
6268
Optional<string> replicaSetName = default(Optional<string>),
6369
Optional<TimeSpan> serverSelectionTimeout = default(Optional<TimeSpan>),
6470
Optional<IServerSelector> preServerSelector = default(Optional<IServerSelector>),
6571
Optional<IServerSelector> postServerSelector = default(Optional<IServerSelector>),
72+
Optional<IReadOnlyDictionary<string, BsonDocument>> schemaMap = default(Optional<IReadOnlyDictionary<string, BsonDocument>>),
6673
Optional<ConnectionStringScheme> scheme = default(Optional<ConnectionStringScheme>))
6774
{
6875
_connectionMode = connectionMode.WithDefault(ClusterConnectionMode.Automatic);
6976
_endPoints = Ensure.IsNotNull(endPoints.WithDefault(__defaultEndPoints), "endPoints").ToList();
77+
_kmsProviders = kmsProviders.WithDefault(null);
7078
_maxServerSelectionWaitQueueSize = Ensure.IsGreaterThanOrEqualToZero(maxServerSelectionWaitQueueSize.WithDefault(500), "maxServerSelectionWaitQueueSize");
7179
_replicaSetName = replicaSetName.WithDefault(null);
7280
_serverSelectionTimeout = Ensure.IsGreaterThanOrEqualToZero(serverSelectionTimeout.WithDefault(TimeSpan.FromSeconds(30)), "serverSelectionTimeout");
7381
_preServerSelector = preServerSelector.WithDefault(null);
7482
_postServerSelector = postServerSelector.WithDefault(null);
7583
_scheme = scheme.WithDefault(ConnectionStringScheme.MongoDB);
84+
_schemaMap = schemaMap.WithDefault(null);
7685
}
7786

7887
// properties
@@ -98,6 +107,17 @@ public IReadOnlyList<EndPoint> EndPoints
98107
get { return _endPoints; }
99108
}
100109

110+
/// <summary>
111+
/// Gets the kms providers.
112+
/// </summary>
113+
/// <value>
114+
/// The kms providers.
115+
/// </value>
116+
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>> KmsProviders
117+
{
118+
get { return _kmsProviders; }
119+
}
120+
101121
/// <summary>
102122
/// Gets the maximum size of the server selection wait queue.
103123
/// </summary>
@@ -120,6 +140,17 @@ public string ReplicaSetName
120140
get { return _replicaSetName; }
121141
}
122142

143+
/// <summary>
144+
/// Gets the schema map.
145+
/// </summary>
146+
/// <value>
147+
/// The schema map.
148+
/// </value>
149+
public IReadOnlyDictionary<string, BsonDocument> SchemaMap
150+
{
151+
get { return _schemaMap; }
152+
}
153+
123154
/// <summary>
124155
/// Gets the connection string scheme.
125156
/// </summary>
@@ -170,31 +201,37 @@ public IServerSelector PostServerSelector
170201
/// </summary>
171202
/// <param name="connectionMode">The connection mode.</param>
172203
/// <param name="endPoints">The end points.</param>
204+
/// <param name="kmsProviders">The kms providers.</param>
173205
/// <param name="maxServerSelectionWaitQueueSize">Maximum size of the server selection wait queue.</param>
174206
/// <param name="replicaSetName">Name of the replica set.</param>
175207
/// <param name="serverSelectionTimeout">The server selection timeout.</param>
176208
/// <param name="preServerSelector">The pre server selector.</param>
177209
/// <param name="postServerSelector">The post server selector.</param>
210+
/// <param name="schemaMap">The schema map.</param>
178211
/// <param name="scheme">The connection string scheme.</param>
179212
/// <returns>A new ClusterSettings instance.</returns>
180213
public ClusterSettings With(
181214
Optional<ClusterConnectionMode> connectionMode = default(Optional<ClusterConnectionMode>),
182215
Optional<IEnumerable<EndPoint>> endPoints = default(Optional<IEnumerable<EndPoint>>),
216+
Optional<IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>>> kmsProviders = default(Optional<IReadOnlyDictionary<string, IReadOnlyDictionary<string, object>>>),
183217
Optional<int> maxServerSelectionWaitQueueSize = default(Optional<int>),
184218
Optional<string> replicaSetName = default(Optional<string>),
185219
Optional<TimeSpan> serverSelectionTimeout = default(Optional<TimeSpan>),
186220
Optional<IServerSelector> preServerSelector = default(Optional<IServerSelector>),
187221
Optional<IServerSelector> postServerSelector = default(Optional<IServerSelector>),
222+
Optional<IReadOnlyDictionary<string, BsonDocument>> schemaMap = default(Optional<IReadOnlyDictionary<string, BsonDocument>>),
188223
Optional<ConnectionStringScheme> scheme = default(Optional<ConnectionStringScheme>))
189224
{
190225
return new ClusterSettings(
191226
connectionMode: connectionMode.WithDefault(_connectionMode),
192227
endPoints: Optional.Enumerable(endPoints.WithDefault(_endPoints)),
228+
kmsProviders: Optional.Create(kmsProviders.WithDefault(_kmsProviders)),
193229
maxServerSelectionWaitQueueSize: maxServerSelectionWaitQueueSize.WithDefault(_maxServerSelectionWaitQueueSize),
194230
replicaSetName: replicaSetName.WithDefault(_replicaSetName),
195231
serverSelectionTimeout: serverSelectionTimeout.WithDefault(_serverSelectionTimeout),
196232
preServerSelector: Optional.Create(preServerSelector.WithDefault(_preServerSelector)),
197233
postServerSelector: Optional.Create(postServerSelector.WithDefault(_postServerSelector)),
234+
schemaMap: Optional.Create(schemaMap.WithDefault(_schemaMap)),
198235
scheme: scheme.WithDefault(_scheme));
199236
}
200237
}

src/MongoDB.Driver.Core/Core/Misc/Feature.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class Feature
4141
private static readonly Feature __bypassDocumentValidation = new Feature("BypassDocumentValidation", new SemanticVersion(3, 2, 0));
4242
private static readonly Feature __changeStreamStage = new Feature("ChangeStreamStage", new SemanticVersion(3, 5, 11));
4343
private static readonly Feature __changeStreamPostBatchResumeToken = new Feature("ChangeStreamPostBatchResumeToken", new SemanticVersion(4, 0 ,7));
44+
private static readonly Feature __clientSideEncryption = new Feature("ClientSideEncryption", new SemanticVersion(4, 1, 9));
4445
private static readonly CollationFeature __collation = new CollationFeature("Collation", new SemanticVersion(3, 3, 11));
4546
private static readonly Feature __commandMessage = new Feature("CommandMessage", new SemanticVersion(3, 6, 0));
4647
private static readonly CommandsThatWriteAcceptWriteConcernFeature __commandsThatWriteAcceptWriteConcern = new CommandsThatWriteAcceptWriteConcernFeature("CommandsThatWriteAcceptWriteConcern", new SemanticVersion(3, 3, 11));
@@ -170,6 +171,11 @@ public class Feature
170171
/// </summary>
171172
public static Feature ChangeStreamPostBatchResumeToken => __changeStreamPostBatchResumeToken;
172173

174+
/// <summary>
175+
/// Gets the client side encryption feature.
176+
/// </summary>
177+
public static Feature ClientSideEncryption => __clientSideEncryption;
178+
173179
/// <summary>
174180
/// Gets the collation feature.
175181
/// </summary>

0 commit comments

Comments
 (0)