Skip to content

Commit eb425d9

Browse files
committed
CSHARP-2163: Make PasswordEvidence implementation FIPS compliant.
1 parent 5e3a9ca commit eb425d9

File tree

6 files changed

+533
-85
lines changed

6 files changed

+533
-85
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/* Copyright 2018-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.Runtime.InteropServices;
18+
using System.Security;
19+
using MongoDB.Bson.IO;
20+
using MongoDB.Driver.Core.Misc;
21+
22+
namespace MongoDB.Driver
23+
{
24+
internal sealed class DecryptedSecureString : IDisposable
25+
{
26+
// private fields
27+
private char[] _chars;
28+
private GCHandle _charsHandle;
29+
private IntPtr _charsIntPtr;
30+
private bool _disposed;
31+
private readonly SecureString _secureString;
32+
private byte[] _utf8Bytes;
33+
private GCHandle _utf8BytesHandle;
34+
35+
// constructors
36+
public DecryptedSecureString(SecureString secureString)
37+
{
38+
_secureString = Ensure.IsNotNull(secureString, nameof(secureString));
39+
}
40+
41+
// finalizer
42+
~DecryptedSecureString()
43+
{
44+
Dispose(false);
45+
}
46+
47+
// public methods
48+
public void Dispose()
49+
{
50+
Dispose(true);
51+
GC.SuppressFinalize(this);
52+
}
53+
54+
public char[] GetChars()
55+
{
56+
if (_chars == null)
57+
{
58+
#if NET45
59+
_charsIntPtr = Marshal.SecureStringToGlobalAllocUnicode(_secureString);
60+
#else
61+
_charsIntPtr = SecureStringMarshal.SecureStringToGlobalAllocUnicode(_secureString);
62+
#endif
63+
_chars = new char[_secureString.Length];
64+
_charsHandle = GCHandle.Alloc(_chars, GCHandleType.Pinned);
65+
Marshal.Copy(_charsIntPtr, _chars, 0, _secureString.Length);
66+
}
67+
68+
return _chars;
69+
}
70+
71+
public byte[] GetUtf8Bytes()
72+
{
73+
if (_utf8Bytes == null)
74+
{
75+
var chars = GetChars();
76+
var encoding = Utf8Encodings.Strict;
77+
_utf8Bytes = new byte[encoding.GetByteCount(chars)];
78+
_utf8BytesHandle = GCHandle.Alloc(_utf8Bytes, GCHandleType.Pinned);
79+
encoding.GetBytes(chars, 0, chars.Length, _utf8Bytes, 0);
80+
}
81+
82+
return _utf8Bytes;
83+
}
84+
85+
// private methods
86+
private void Dispose(bool disposing)
87+
{
88+
if (!_disposed)
89+
{
90+
if (disposing)
91+
{
92+
if (_utf8Bytes != null)
93+
{
94+
Array.Clear(_utf8Bytes, 0, _utf8Bytes.Length);
95+
}
96+
if (_chars != null)
97+
{
98+
Array.Clear(_chars, 0, _chars.Length);
99+
}
100+
}
101+
if (_utf8BytesHandle.IsAllocated)
102+
{
103+
_utf8BytesHandle.Free();
104+
}
105+
if (_charsHandle.IsAllocated)
106+
{
107+
_charsHandle.Free();
108+
}
109+
if (_charsIntPtr != IntPtr.Zero)
110+
{
111+
Marshal.ZeroFreeGlobalAllocUnicode(_charsIntPtr);
112+
_charsIntPtr = IntPtr.Zero; // to facilitate testing
113+
}
114+
_disposed = true;
115+
}
116+
}
117+
}
118+
}

src/MongoDB.Driver/MongoDB.Driver.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@
106106
<Compile Include="ClientSessionWrappingCoreSession.cs" />
107107
<Compile Include="CreateIndexModel.cs" />
108108
<Compile Include="CreateViewOptions.cs" />
109+
<Compile Include="DecryptedSecureString.cs" />
109110
<Compile Include="DeleteOptions.cs" />
110111
<Compile Include="FieldValueSerializerHelper.cs" />
111112
<Compile Include="FilteredMongoCollectionBase.cs" />

src/MongoDB.Driver/PasswordEvidence.cs

Lines changed: 25 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818
using System.Runtime.InteropServices;
1919
using System.Security;
2020
using System.Security.Cryptography;
21-
using System.Text;
2221
using MongoDB.Bson;
2322
using MongoDB.Bson.IO;
2423
using MongoDB.Driver.Core.Misc;
24+
using MongoDB.Shared;
2525

2626
namespace MongoDB.Driver
2727
{
@@ -32,7 +32,6 @@ public sealed class PasswordEvidence : MongoIdentityEvidence
3232
{
3333
// private fields
3434
private readonly SecureString _securePassword;
35-
private readonly string _digest; // used to implement Equals without referring to the SecureString
3635

3736
// constructors
3837
/// <summary>
@@ -41,18 +40,20 @@ public sealed class PasswordEvidence : MongoIdentityEvidence
4140
/// <param name="password">The password.</param>
4241
public PasswordEvidence(SecureString password)
4342
{
43+
Ensure.IsNotNull(password, nameof(password));
4444
_securePassword = password.Copy();
4545
_securePassword.MakeReadOnly();
46-
_digest = GenerateDigest(password);
4746
}
4847

4948
/// <summary>
5049
/// Initializes a new instance of the <see cref="PasswordEvidence" /> class.
5150
/// </summary>
5251
/// <param name="password">The password.</param>
5352
public PasswordEvidence(string password)
54-
: this(CreateSecureString(password))
55-
{ }
53+
{
54+
Ensure.IsNotNull(password, nameof(password));
55+
_securePassword = CreateSecureString(password);
56+
}
5657

5758
// public properties
5859
/// <summary>
@@ -74,18 +75,26 @@ public SecureString SecurePassword
7475
public override bool Equals(object rhs)
7576
{
7677
if (object.ReferenceEquals(rhs, null) || GetType() != rhs.GetType()) { return false; }
77-
return _digest == ((PasswordEvidence)rhs)._digest;
78+
79+
using (var lhsDecryptedPassword = new DecryptedSecureString(_securePassword))
80+
using (var rhsDecryptedPassword = new DecryptedSecureString(((PasswordEvidence)rhs)._securePassword))
81+
{
82+
return lhsDecryptedPassword.GetChars().SequenceEqual(rhsDecryptedPassword.GetChars());
83+
}
7884
}
7985

8086
/// <summary>
8187
/// Returns a hash code for this instance.
8288
/// </summary>
8389
/// <returns>
84-
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
90+
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
8591
/// </returns>
8692
public override int GetHashCode()
8793
{
88-
return _digest.GetHashCode();
94+
using (var decryptedPassword = new DecryptedSecureString(_securePassword))
95+
{
96+
return new Hasher().HashStructElements(decryptedPassword.GetChars()).GetHashCode();
97+
}
8998
}
9099

91100
// internal methods
@@ -97,94 +106,25 @@ public override int GetHashCode()
97106
internal string ComputeMongoCRPasswordDigest(string username)
98107
{
99108
using (var md5 = MD5.Create())
109+
using (var decryptedPassword = new DecryptedSecureString(_securePassword))
100110
{
101111
var encoding = Utf8Encodings.Strict;
102112
var prefixBytes = encoding.GetBytes(username + ":mongo:");
103-
var hash = ComputeHash(md5, prefixBytes, _securePassword);
113+
var hash = ComputeHash(md5, prefixBytes, decryptedPassword.GetUtf8Bytes());
104114
return BsonUtils.ToHexString(hash);
105115
}
106116
}
107117

108118
// private static methods
109-
private static SecureString CreateSecureString(string str)
110-
{
111-
if (str != null)
112-
{
113-
var secureStr = new SecureString();
114-
foreach (var c in str)
115-
{
116-
secureStr.AppendChar(c);
117-
}
118-
secureStr.MakeReadOnly();
119-
return secureStr;
120-
}
121-
122-
return null;
123-
}
124-
125-
/// <summary>
126-
/// Computes the hash value of the secured string
127-
/// </summary>
128-
private static string GenerateDigest(SecureString password)
129-
{
130-
using (var sha256 = SHA256.Create())
131-
{
132-
var hash = ComputeHash(sha256, new byte[0], password);
133-
return BsonUtils.ToHexString(hash);
134-
}
135-
}
136-
137-
private static byte[] ComputeHash(HashAlgorithm algorithm, byte[] prefixBytes, SecureString password)
119+
private static SecureString CreateSecureString(string value)
138120
{
139-
if (password.Length == 0)
140-
{
141-
return ComputeHash(algorithm, prefixBytes, new byte[0]);
142-
}
143-
else
144-
{
145-
#if NET45
146-
var passwordIntPtr = Marshal.SecureStringToGlobalAllocUnicode(password);
147-
#else
148-
var passwordIntPtr = SecureStringMarshal.SecureStringToGlobalAllocUnicode(password);
149-
#endif
150-
try
151-
{
152-
var passwordChars = new char[password.Length];
153-
var passwordCharsHandle = GCHandle.Alloc(passwordChars, GCHandleType.Pinned);
154-
try
155-
{
156-
Marshal.Copy(passwordIntPtr, passwordChars, 0, password.Length);
157-
158-
return ComputeHash(algorithm, prefixBytes, passwordChars);
159-
}
160-
finally
161-
{
162-
Array.Clear(passwordChars, 0, passwordChars.Length);
163-
passwordCharsHandle.Free();
164-
}
165-
}
166-
finally
167-
{
168-
Marshal.ZeroFreeGlobalAllocUnicode(passwordIntPtr);
169-
}
170-
}
171-
}
172-
173-
private static byte[] ComputeHash(HashAlgorithm algorithm, byte[] prefixBytes, char[] passwordChars)
174-
{
175-
var passwordBytes = new byte[Utf8Encodings.Strict.GetByteCount(passwordChars)];
176-
var passwordBytesHandle = GCHandle.Alloc(passwordBytes, GCHandleType.Pinned);
177-
try
178-
{
179-
Utf8Encodings.Strict.GetBytes(passwordChars, 0, passwordChars.Length, passwordBytes, 0);
180-
181-
return ComputeHash(algorithm, prefixBytes, passwordBytes);
182-
}
183-
finally
121+
var secureString = new SecureString();
122+
foreach (var c in value)
184123
{
185-
Array.Clear(passwordBytes, 0, passwordBytes.Length);
186-
passwordBytesHandle.Free();
124+
secureString.AppendChar(c);
187125
}
126+
secureString.MakeReadOnly();
127+
return secureString;
188128
}
189129

190130
private static byte[] ComputeHash(HashAlgorithm algorithm, byte[] prefixBytes, byte[] passwordBytes)

0 commit comments

Comments
 (0)