Skip to content

Commit 7c17a7d

Browse files
author
rstam
committed
CSHARP-658: Add support for sending client SSL certificates.
1 parent 13966b7 commit 7c17a7d

13 files changed

+1162
-135
lines changed

MongoDB.Driver/Communication/MongoConnection.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
using System.IO;
1818
using System.Net.Security;
1919
using System.Net.Sockets;
20+
using System.Security.Authentication;
2021
using System.Security.Cryptography.X509Certificates;
2122
using MongoDB.Bson;
2223
using MongoDB.Bson.IO;
2324
using MongoDB.Bson.Serialization;
24-
using MongoDB.Driver.Communication;
2525
using MongoDB.Driver.Communication.Security;
2626

2727
namespace MongoDB.Driver.Internal
@@ -195,19 +195,32 @@ internal void Open()
195195
var stream = (Stream)tcpClient.GetStream();
196196
if (_serverInstance.Settings.UseSsl)
197197
{
198-
SslStream sslStream;
199-
if (_serverInstance.Settings.VerifySslCertificate)
198+
var checkCertificateRevocation = true;
199+
var clientCertificateCollection = (X509CertificateCollection)null;
200+
var clientCertificateSelectionCallback = (LocalCertificateSelectionCallback)null;
201+
var enabledSslProtocols = SslProtocols.Default;
202+
var serverCertificateValidationCallback = (RemoteCertificateValidationCallback)null;
203+
204+
var sslSettings = _serverInstance.Settings.SslSettings;
205+
if (sslSettings != null)
200206
{
201-
sslStream = new SslStream(stream, false); // don't leave inner stream open
207+
checkCertificateRevocation = sslSettings.CheckCertificateRevocation;
208+
clientCertificateCollection = sslSettings.ClientCertificateCollection;
209+
clientCertificateSelectionCallback = sslSettings.ClientCertificateSelectionCallback;
210+
enabledSslProtocols = sslSettings.EnabledSslProtocols;
211+
serverCertificateValidationCallback = sslSettings.ServerCertificateValidationCallback;
202212
}
203-
else
213+
214+
if (serverCertificateValidationCallback == null && !_serverInstance.Settings.VerifySslCertificate)
204215
{
205-
sslStream = new SslStream(stream, false, AcceptAnyCertificate, null); // don't leave inner stream open
216+
serverCertificateValidationCallback = AcceptAnyCertificate;
206217
}
207218

219+
var sslStream = new SslStream(stream, false, serverCertificateValidationCallback, clientCertificateSelectionCallback);
208220
try
209221
{
210-
sslStream.AuthenticateAsClient(_serverInstance.Address.Host);
222+
var targetHost = _serverInstance.Address.Host;
223+
sslStream.AuthenticateAsClient(targetHost, clientCertificateCollection, enabledSslProtocols, checkCertificateRevocation);
211224
}
212225
catch
213226
{

MongoDB.Driver/MongoClientSettings.cs

Lines changed: 113 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@
1919
using System.Linq;
2020
using System.Text;
2121
using MongoDB.Bson;
22+
using MongoDB.Shared;
2223

2324
namespace MongoDB.Driver
2425
{
2526
/// <summary>
2627
/// The settings for a MongoDB client.
2728
/// </summary>
28-
public class MongoClientSettings
29+
public class MongoClientSettings : IEquatable<MongoClientSettings>
2930
{
3031
// private fields
3132
private ConnectionMode _connectionMode;
@@ -42,6 +43,7 @@ public class MongoClientSettings
4243
private TimeSpan _secondaryAcceptableLatency;
4344
private List<MongoServerAddress> _servers;
4445
private TimeSpan _socketTimeout;
46+
private SslSettings _sslSettings;
4547
private bool _useSsl;
4648
private bool _verifySslCertificate;
4749
private int _waitQueueSize;
@@ -73,6 +75,7 @@ public MongoClientSettings()
7375
_secondaryAcceptableLatency = MongoDefaults.SecondaryAcceptableLatency;
7476
_servers = new List<MongoServerAddress> { new MongoServerAddress("localhost") };
7577
_socketTimeout = MongoDefaults.SocketTimeout;
78+
_sslSettings = null;
7679
_useSsl = false;
7780
_verifySslCertificate = true;
7881
_waitQueueSize = MongoDefaults.ComputedWaitQueueSize;
@@ -301,6 +304,19 @@ public TimeSpan SocketTimeout
301304
}
302305
}
303306

307+
/// <summary>
308+
/// Gets or sets the SSL settings.
309+
/// </summary>
310+
public SslSettings SslSettings
311+
{
312+
get { return _sslSettings; }
313+
set
314+
{
315+
if (_isFrozen) { throw new InvalidOperationException("MongoClientSettings is frozen."); }
316+
_sslSettings = value;
317+
}
318+
}
319+
304320
/// <summary>
305321
/// Gets or sets whether to use SSL.
306322
/// </summary>
@@ -370,6 +386,33 @@ public WriteConcern WriteConcern
370386
}
371387
}
372388

389+
// public operators
390+
/// <summary>
391+
/// Determines whether two <see cref="MongoClientSettings"/> instances are equal.
392+
/// </summary>
393+
/// <param name="lhs">The LHS.</param>
394+
/// <param name="rhs">The RHS.</param>
395+
/// <returns>
396+
/// <c>true</c> if the left hand side is equal to the right hand side; otherwise, <c>false</c>.
397+
/// </returns>
398+
public static bool operator ==(MongoClientSettings lhs, MongoClientSettings rhs)
399+
{
400+
return object.Equals(lhs, rhs); // handles lhs == null correctly
401+
}
402+
403+
/// <summary>
404+
/// Determines whether two <see cref="MongoClientSettings"/> instances are not equal.
405+
/// </summary>
406+
/// <param name="lhs">The LHS.</param>
407+
/// <param name="rhs">The RHS.</param>
408+
/// <returns>
409+
/// <c>true</c> if the left hand side is not equal to the right hand side; otherwise, <c>false</c>.
410+
/// </returns>
411+
public static bool operator !=(MongoClientSettings lhs, MongoClientSettings rhs)
412+
{
413+
return !(lhs == rhs);
414+
}
415+
373416
// public static methods
374417
/// <summary>
375418
/// Gets a MongoClientSettings object intialized with values from a connection string builder.
@@ -403,6 +446,7 @@ public static MongoClientSettings FromConnectionStringBuilder(MongoConnectionStr
403446
clientSettings.SecondaryAcceptableLatency = builder.SecondaryAcceptableLatency;
404447
clientSettings.Servers = new List<MongoServerAddress>(builder.Servers);
405448
clientSettings.SocketTimeout = builder.SocketTimeout;
449+
clientSettings.SslSettings = null; // SSL settings must be provided in code
406450
clientSettings.UseSsl = builder.UseSsl;
407451
clientSettings.VerifySslCertificate = builder.VerifySslCertificate;
408452
clientSettings.WaitQueueSize = builder.ComputedWaitQueueSize;
@@ -443,6 +487,7 @@ public static MongoClientSettings FromUrl(MongoUrl url)
443487
clientSettings.SecondaryAcceptableLatency = url.SecondaryAcceptableLatency;
444488
clientSettings.Servers = new List<MongoServerAddress>(url.Servers);
445489
clientSettings.SocketTimeout = url.SocketTimeout;
490+
clientSettings.SslSettings = null; // SSL settings must be provided in code
446491
clientSettings.UseSsl = url.UseSsl;
447492
clientSettings.VerifySslCertificate = url.VerifySslCertificate;
448493
clientSettings.WaitQueueSize = url.ComputedWaitQueueSize;
@@ -473,6 +518,7 @@ public MongoClientSettings Clone()
473518
clone._secondaryAcceptableLatency = _secondaryAcceptableLatency;
474519
clone._servers = new List<MongoServerAddress>(_servers);
475520
clone._socketTimeout = _socketTimeout;
521+
clone._sslSettings = (_sslSettings == null) ? null : _sslSettings.Clone();
476522
clone._useSsl = _useSsl;
477523
clone._verifySslCertificate = _verifySslCertificate;
478524
clone._waitQueueSize = _waitQueueSize;
@@ -482,47 +528,49 @@ public MongoClientSettings Clone()
482528
}
483529

484530
/// <summary>
485-
/// Compares two MongoClientSettings instances.
531+
/// Determines whether the specified <see cref="MongoClientSettings" /> is equal to this instance.
486532
/// </summary>
487-
/// <param name="obj">The other instance.</param>
488-
/// <returns>True if the two instances are equal.</returns>
533+
/// <param name="obj">The <see cref="MongoClientSettings" /> to compare with this instance.</param>
534+
/// <returns>
535+
/// <c>true</c> if the specified <see cref="MongoClientSettings" /> is equal to this instance; otherwise, <c>false</c>.
536+
/// </returns>
537+
public bool Equals(MongoClientSettings obj)
538+
{
539+
return Equals((object)obj); // handles obj == null correctly
540+
}
541+
542+
/// <summary>
543+
/// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
544+
/// </summary>
545+
/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
546+
/// <returns>
547+
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
548+
/// </returns>
489549
public override bool Equals(object obj)
490550
{
491-
var rhs = obj as MongoClientSettings;
492-
if (rhs == null)
493-
{
494-
return false;
495-
}
496-
else
497-
{
498-
if (_isFrozen && rhs._isFrozen)
499-
{
500-
return _frozenStringRepresentation == rhs._frozenStringRepresentation;
501-
}
502-
else
503-
{
504-
return
505-
_connectionMode == rhs._connectionMode &&
506-
_connectTimeout == rhs._connectTimeout &&
507-
_credentials == rhs._credentials &&
508-
_guidRepresentation == rhs._guidRepresentation &&
509-
_ipv6 == rhs._ipv6 &&
510-
_maxConnectionIdleTime == rhs._maxConnectionIdleTime &&
511-
_maxConnectionLifeTime == rhs._maxConnectionLifeTime &&
512-
_maxConnectionPoolSize == rhs._maxConnectionPoolSize &&
513-
_minConnectionPoolSize == rhs._minConnectionPoolSize &&
514-
_readPreference == rhs._readPreference &&
515-
_replicaSetName == rhs._replicaSetName &&
516-
_secondaryAcceptableLatency == rhs._secondaryAcceptableLatency &&
517-
_servers.SequenceEqual(rhs._servers) &&
518-
_socketTimeout == rhs._socketTimeout &&
519-
_useSsl == rhs._useSsl &&
520-
_verifySslCertificate == rhs._verifySslCertificate &&
521-
_waitQueueSize == rhs._waitQueueSize &&
522-
_waitQueueTimeout == rhs._waitQueueTimeout &&
523-
_writeConcern == rhs._writeConcern;
524-
}
525-
}
551+
if (object.ReferenceEquals(obj, null) || GetType() != obj.GetType()) { return false; }
552+
var rhs = (MongoClientSettings)obj;
553+
return
554+
_connectionMode == rhs._connectionMode &&
555+
_connectTimeout == rhs._connectTimeout &&
556+
_credentials == rhs._credentials &&
557+
_guidRepresentation == rhs._guidRepresentation &&
558+
_ipv6 == rhs._ipv6 &&
559+
_maxConnectionIdleTime == rhs._maxConnectionIdleTime &&
560+
_maxConnectionLifeTime == rhs._maxConnectionLifeTime &&
561+
_maxConnectionPoolSize == rhs._maxConnectionPoolSize &&
562+
_minConnectionPoolSize == rhs._minConnectionPoolSize &&
563+
_readPreference == rhs._readPreference &&
564+
_replicaSetName == rhs._replicaSetName &&
565+
_secondaryAcceptableLatency == rhs._secondaryAcceptableLatency &&
566+
_servers.SequenceEqual(rhs._servers) &&
567+
_socketTimeout == rhs._socketTimeout &&
568+
_sslSettings == rhs._sslSettings &&
569+
_useSsl == rhs._useSsl &&
570+
_verifySslCertificate == rhs._verifySslCertificate &&
571+
_waitQueueSize == rhs._waitQueueSize &&
572+
_waitQueueTimeout == rhs._waitQueueTimeout &&
573+
_writeConcern == rhs._writeConcern;
526574
}
527575

528576
/// <summary>
@@ -569,31 +617,28 @@ public override int GetHashCode()
569617
return _frozenHashCode;
570618
}
571619

572-
// see Effective Java by Joshua Bloch
573-
int hash = 17;
574-
hash = 37 * hash + _connectionMode.GetHashCode();
575-
hash = 37 * hash + _connectTimeout.GetHashCode();
576-
hash = 37 * hash + _credentials.GetHashCode();
577-
hash = 37 * hash + _guidRepresentation.GetHashCode();
578-
hash = 37 * hash + _ipv6.GetHashCode();
579-
hash = 37 * hash + _maxConnectionIdleTime.GetHashCode();
580-
hash = 37 * hash + _maxConnectionLifeTime.GetHashCode();
581-
hash = 37 * hash + _maxConnectionPoolSize.GetHashCode();
582-
hash = 37 * hash + _minConnectionPoolSize.GetHashCode();
583-
hash = 37 * hash + _readPreference.GetHashCode();
584-
hash = 37 * hash + ((_replicaSetName == null) ? 0 : _replicaSetName.GetHashCode());
585-
hash = 37 * hash + _secondaryAcceptableLatency.GetHashCode();
586-
foreach (var server in _servers)
587-
{
588-
hash = 37 * hash + server.GetHashCode();
589-
}
590-
hash = 37 * hash + _socketTimeout.GetHashCode();
591-
hash = 37 * hash + _useSsl.GetHashCode();
592-
hash = 37 * hash + _verifySslCertificate.GetHashCode();
593-
hash = 37 * hash + _waitQueueSize.GetHashCode();
594-
hash = 37 * hash + _waitQueueTimeout.GetHashCode();
595-
hash = 37 * hash + _writeConcern.GetHashCode();
596-
return hash;
620+
return new Hasher()
621+
.Hash(_connectionMode)
622+
.Hash(_connectTimeout)
623+
.Hash(_credentials)
624+
.Hash(_guidRepresentation)
625+
.Hash(_ipv6)
626+
.Hash(_maxConnectionIdleTime)
627+
.Hash(_maxConnectionLifeTime)
628+
.Hash(_maxConnectionPoolSize)
629+
.Hash(_minConnectionPoolSize)
630+
.Hash(_readPreference)
631+
.Hash(_replicaSetName)
632+
.Hash(_secondaryAcceptableLatency)
633+
.HashElements(_servers)
634+
.Hash(_socketTimeout)
635+
.Hash(_sslSettings)
636+
.Hash(_useSsl)
637+
.Hash(_verifySslCertificate)
638+
.Hash(_waitQueueSize)
639+
.Hash(_waitQueueTimeout)
640+
.Hash(_writeConcern)
641+
.GetHashCode();
597642
}
598643

599644
/// <summary>
@@ -622,6 +667,10 @@ public override string ToString()
622667
sb.AppendFormat("SecondaryAcceptableLatency={0};", _secondaryAcceptableLatency);
623668
sb.AppendFormat("Servers={0};", string.Join(",", _servers.Select(s => s.ToString()).ToArray()));
624669
sb.AppendFormat("SocketTimeout={0};", _socketTimeout);
670+
if (_sslSettings != null)
671+
{
672+
sb.AppendFormat("SslSettings={0};", _sslSettings);
673+
}
625674
sb.AppendFormat("Ssl={0};", _useSsl);
626675
sb.AppendFormat("SslVerifyCertificate={0};", _verifySslCertificate);
627676
sb.AppendFormat("WaitQueueSize={0};", _waitQueueSize);

MongoDB.Driver/MongoDB.Driver.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@
8080
<Compile Include="..\GlobalAssemblyInfo.cs">
8181
<Link>Properties\GlobalAssemblyInfo.cs</Link>
8282
</Compile>
83+
<Compile Include="..\MongoDB.Shared\Hasher.cs">
84+
<Link>Hasher.cs</Link>
85+
</Compile>
8386
<Compile Include="Builders\BuilderBase.cs" />
8487
<Compile Include="Builders\CollectionOptionsBuilder.cs" />
8588
<Compile Include="Builders\FieldsBuilder.cs" />
@@ -188,6 +191,7 @@
188191
<Compile Include="ReplicaSetTag.cs" />
189192
<Compile Include="ReplicaSetTagSet.cs" />
190193
<Compile Include="Setting.cs" />
194+
<Compile Include="SslSettings.cs" />
191195
<Compile Include="SystemProfileInfo.cs" />
192196
<Compile Include="Wrappers\BaseWrapper.cs" />
193197
<Compile Include="Wrappers\CollectionOptionsDocument.cs" />

0 commit comments

Comments
 (0)