Skip to content

Commit aca20b6

Browse files
committed
CSHARP-2160: Implement SASLPrep
1 parent ad03b1d commit aca20b6

File tree

13 files changed

+1189
-7
lines changed

13 files changed

+1189
-7
lines changed

Release Notes/Release Notes v2.7.0.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# .NET Driver Version 2.7.0 Release Notes
2+
3+
## Known Issues:
4+
* Incomplete SCRAM-SHA-256 Support in .NET Standard: In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is not fully implemented due to the lack of a string normalization function in .NET Standard 1.6. Normalizing the password into Unicode Normalization Form KC beforehand MAY help. SCRAM-SHA-1 is the recommended alternative for now.
5+
6+
An online version of these release notes is available at:
7+
8+
https://github.com/mongodb/mongo-csharp-driver/blob/master/Release%20Notes/Release%20Notes%20v2.7.0.md
9+
10+
The full list of JIRA issues that are currently scheduled to be resolved in this release is available at:
11+
12+
https://jira.mongodb.org/issues/?jql=project%20%3D%20CSHARP%20AND%20fixVersion%20%3D%202.7.0%20ORDER%20BY%20key%20ASC
13+
14+
The list may change as we approach the release date.
15+
16+
Documentation on the .NET driver can be found at:
17+
18+
http://mongodb.github.io/mongo-csharp-driver/
19+
20+
Upgrading
21+
22+
There are no known backwards breaking changes in this release.

THIRD-PARTY-NOTICES

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ For any licenses that require disclosure of source, sources are available at
1212
https://github.com/mongodb/mongo-csharp-driver.
1313

1414

15-
1) The following files: CryptographyHelpers.cs, HashAlgorithmName.cs, Rfc2898DeriveBytes.cs
15+
1. The following files: CryptographyHelpers.cs, HashAlgorithmName.cs, Rfc2898DeriveBytes.cs
1616

1717
Original work:
1818
Copyright (c) 2016–2017 .NET Foundation and Contributors
@@ -54,3 +54,35 @@ https://github.com/mongodb/mongo-csharp-driver.
5454
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5555
See the License for the specific language governing permissions and
5656
limitations under the License.
57+
58+
2. SaslPrepHelper.cs
59+
60+
Original work:
61+
Copyright (c) 2017 Tom Bentley
62+
63+
Licensed under the Apache License, Version 2.0 (the "License");
64+
you may not use this file except in compliance with the License.
65+
You may obtain a copy of the License at
66+
67+
http://www.apache.org/licenses/LICENSE-2.0
68+
69+
Unless required by applicable law or agreed to in writing, software
70+
distributed under the License is distributed on an "AS IS" BASIS,
71+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
72+
See the License for the specific language governing permissions and
73+
limitations under the License.
74+
75+
Modified work:
76+
Copyright (c) 2018–present MongoDB Inc.
77+
78+
Licensed under the Apache License, Version 2.0 (the "License");
79+
you may not use this file except in compliance with the License.
80+
You may obtain a copy of the License at
81+
82+
http://www.apache.org/licenses/LICENSE-2.0
83+
84+
Unless required by applicable law or agreed to in writing, software
85+
distributed under the License is distributed on an "AS IS" BASIS,
86+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
87+
See the License for the specific language governing permissions and
88+
limitations under the License.

src/MongoDB.Driver.Core/Core/Authentication/SaslPrepHelper.cs

Lines changed: 805 additions & 0 deletions
Large diffs are not rendered by default.

src/MongoDB.Driver.Core/Core/Authentication/ScramSha256Authenticator.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ namespace MongoDB.Driver.Core.Authentication
3333
{
3434
/// <summary>
3535
/// A SCRAM-SHA256 SASL authenticator.
36+
/// In .NET Standard, this class does not normalize the password in the credentials, so non-ASCII
37+
/// passwords may not work unless they are normalized into Unicode Normalization Form KC beforehand.
3638
/// </summary>
3739
public sealed class ScramSha256Authenticator : ScramShaAuthenticator
3840
{
@@ -48,6 +50,8 @@ public sealed class ScramSha256Authenticator : ScramShaAuthenticator
4850
// constructors
4951
/// <summary>
5052
/// Initializes a new instance of the <see cref="ScramSha256Authenticator"/> class.
53+
/// In .NET Standard, this class does not normalize the password in <paramref name="credential"/>, so non-ASCII
54+
/// passwords may not work unless they are normalized into Unicode Normalization Form KC beforehand.
5155
/// </summary>
5256
/// <param name="credential">The credential.</param>
5357
public ScramSha256Authenticator(UsernamePasswordCredential credential)
@@ -71,17 +75,17 @@ private static byte[] H256(byte[] data)
7175
private static byte[] Hi256(UsernamePasswordCredential credential, byte[] salt, int iterations)
7276
{
7377
#if NET45
74-
var passwordIntPtr = Marshal.SecureStringToGlobalAllocUnicode(credential.Password);
78+
var passwordIntPtr = Marshal.SecureStringToGlobalAllocUnicode(credential.SaslPreppedPassword);
7579
#else
76-
var passwordIntPtr = SecureStringMarshal.SecureStringToGlobalAllocUnicode(credential.Password);
80+
var passwordIntPtr = SecureStringMarshal.SecureStringToGlobalAllocUnicode(credential.SaslPreppedPassword);
7781
#endif
7882
try
7983
{
80-
var passwordChars = new char[credential.Password.Length];
84+
var passwordChars = new char[credential.SaslPreppedPassword.Length];
8185
var passwordCharsHandle = GCHandle.Alloc(passwordChars, GCHandleType.Pinned);
8286
try
8387
{
84-
Marshal.Copy(passwordIntPtr, passwordChars, 0, credential.Password.Length);
88+
Marshal.Copy(passwordIntPtr, passwordChars, 0, credential.SaslPreppedPassword.Length);
8589
return Hi256(passwordChars, salt, iterations);
8690
}
8791
finally

src/MongoDB.Driver.Core/Core/Authentication/UsernamePasswordCredential.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace MongoDB.Driver.Core.Authentication
2626
public sealed class UsernamePasswordCredential
2727
{
2828
// fields
29+
private readonly Lazy<SecureString> _saslPreppedPassword;
2930
private string _source;
3031
private SecureString _password;
3132
private string _username;
@@ -40,10 +41,19 @@ public sealed class UsernamePasswordCredential
4041
public UsernamePasswordCredential(string source, string username, string password)
4142
: this(source, username, ConvertPasswordToSecureString(password))
4243
{
44+
// Compute saslPreppedPassword immediately and store it securely while the password is already in
45+
// managed memory. We don't create a closure over the password so that it will hopefully get
46+
// garbage-collected sooner rather than later.
47+
var saslPreppedPassword = ConvertPasswordToSecureString(SaslPrepHelper.SaslPrepStored(password));
48+
_saslPreppedPassword = new Lazy<SecureString>(() => saslPreppedPassword);
4349
}
4450

4551
/// <summary>
4652
/// Initializes a new instance of the <see cref="UsernamePasswordCredential"/> class.
53+
/// Less secure when used in conjunction with SCRAM-SHA-256, due to the need to store the password in a managed
54+
/// string in order to SaslPrep it.
55+
/// See <a href="https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst#scram-sha-256">Driver Authentication: SCRAM-SHA-256</a>
56+
/// for additional details.
4757
/// </summary>
4858
/// <param name="source">The source.</param>
4959
/// <param name="username">The username.</param>
@@ -53,6 +63,10 @@ public UsernamePasswordCredential(string source, string username, SecureString p
5363
_source = Ensure.IsNotNullOrEmpty(source, nameof(source));
5464
_username = Ensure.IsNotNullOrEmpty(username, nameof(username));
5565
_password = Ensure.IsNotNull(password, nameof(password));
66+
// defer computing the saslPreppedPassword until we need to since this will leak the password into managed
67+
// memory
68+
_saslPreppedPassword = new Lazy<SecureString>(
69+
() => ConvertPasswordToSecureString(SaslPrepHelper.SaslPrepStored(GetInsecurePassword())));
5670
}
5771

5872
// properties
@@ -67,6 +81,17 @@ public SecureString Password
6781
get { return _password; }
6882
}
6983

84+
/// <summary>
85+
/// Gets the the SASLprepped password.
86+
/// May create a cleartext copy of the password in managed memory the first time it is accessed.
87+
/// Use only as needed e.g. for SCRAM-SHA-256.
88+
/// </summary>
89+
/// <returns>The SASLprepped password.</returns>
90+
public SecureString SaslPreppedPassword
91+
{
92+
get { return _saslPreppedPassword.Value; }
93+
}
94+
7095
/// <summary>
7196
/// Gets the source.
7297
/// </summary>

src/MongoDB.Driver.Core/MongoDB.Driver.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
<Compile Include="Core\Authentication\Sspi\SspiHandle.cs" />
9696
<Compile Include="Core\Authentication\Sspi\SspiPackage.cs" />
9797
<Compile Include="Core\Authentication\Sspi\Win32Exception.cs" />
98+
<Compile Include="Core\Authentication\SaslPrepHelper.cs" />
9899
<Compile Include="Core\Authentication\Vendored\CryptographyHelpers.cs" />
99100
<Compile Include="Core\Authentication\Vendored\HashAlgorithmName.cs" />
100101
<Compile Include="Core\Authentication\Vendored\Rfc2898DeriveBytes.cs" />

src/MongoDB.Driver/MongoClient.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public MongoClient()
6767

6868
/// <summary>
6969
/// Initializes a new instance of the MongoClient class.
70+
/// In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is
71+
/// not fully implemented due to the lack of a string normalization function in .NET Standard 1.6.
72+
/// Normalizing the password into Unicode Normalization Form KC beforehand MAY help.
73+
/// SCRAM-SHA-1 is the recommended alternative for now.
7074
/// </summary>
7175
/// <param name="settings">The settings.</param>
7276
public MongoClient(MongoClientSettings settings)
@@ -78,6 +82,10 @@ public MongoClient(MongoClientSettings settings)
7882

7983
/// <summary>
8084
/// Initializes a new instance of the MongoClient class.
85+
/// In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is
86+
/// not fully implemented due to the lack of a string normalization function in .NET Standard 1.6.
87+
/// Normalizing the password into Unicode Normalization Form KC beforehand MAY help.
88+
/// SCRAM-SHA-1 is the recommended alternative for now.
8189
/// </summary>
8290
/// <param name="url">The URL.</param>
8391
public MongoClient(MongoUrl url)
@@ -87,6 +95,10 @@ public MongoClient(MongoUrl url)
8795

8896
/// <summary>
8997
/// Initializes a new instance of the MongoClient class.
98+
/// In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is
99+
/// not fully implemented due to the lack of a string normalization function in .NET Standard 1.6.
100+
/// Normalizing the password into Unicode Normalization Form KC beforehand MAY help.
101+
/// SCRAM-SHA-1 is the recommended alternative for now.
90102
/// </summary>
91103
/// <param name="connectionString">The connection string.</param>
92104
public MongoClient(string connectionString)

src/MongoDB.Driver/MongoCredential.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ public class MongoCredential : IEquatable<MongoCredential>
4141
/// <summary>
4242
/// Initializes a new instance of the <see cref="MongoCredential" /> class.
4343
/// </summary>
44-
/// <param name="mechanism">Mechanism to authenticate with.</param>
44+
/// <param name="mechanism">Mechanism to authenticate with.
45+
/// In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is
46+
/// not fully implemented due to the lack of a string normalization function in .NET Standard 1.6.
47+
/// Normalizing the password into Unicode Normalization Form KC beforehand MAY help.
48+
/// SCRAM-SHA-1 is the recommended alternative for now.</param>
4549
/// <param name="identity">The identity.</param>
4650
/// <param name="evidence">The evidence.</param>
4751
public MongoCredential(string mechanism, MongoIdentity identity, MongoIdentityEvidence evidence)
@@ -80,6 +84,10 @@ public MongoIdentity Identity
8084

8185
/// <summary>
8286
/// Gets the mechanism to authenticate with.
87+
/// In .NET Standard, authenticating via SCRAM-SHA-256 may not work with non-ASCII passwords because SaslPrep is
88+
/// not fully implemented due to the lack of a string normalization function in .NET Standard 1.6.
89+
/// Normalizing the password into Unicode Normalization Form KC beforehand MAY help.
90+
/// SCRAM-SHA-1 is the recommended alternative for now.
8391
/// </summary>
8492
public string Mechanism
8593
{

src/MongoDB.Driver/PasswordEvidence.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ public sealed class PasswordEvidence : MongoIdentityEvidence
3636
// constructors
3737
/// <summary>
3838
/// Initializes a new instance of the <see cref="PasswordEvidence" /> class.
39+
/// Less secure when used in conjunction with SCRAM-SHA-256, due to the need to store the password in a managed
40+
/// string in order to SaslPrep it.
41+
/// See <a href="https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst#scram-sha-256">Driver Authentication: SCRAM-SHA-256</a>
42+
/// for additional details.
3943
/// </summary>
4044
/// <param name="password">The password.</param>
4145
public PasswordEvidence(SecureString password)

tests/MongoDB.Bson.TestHelpers/Reflector.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ public static object GetPropertyValue(object obj, string name, BindingFlags flag
3232
var propertyInfo = obj.GetType().GetProperty(name, flags);
3333
return propertyInfo.GetValue(obj);
3434
}
35+
36+
public static object GetStaticFieldValue(Type type, string name, BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static)
37+
{
38+
var fieldInfo = GetDeclaredOrInheritedField(type, name, flags);
39+
return fieldInfo.GetValue(null);
40+
}
3541

3642
public static object Invoke(object obj, string name, BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance)
3743
{
@@ -63,6 +69,23 @@ public static object Invoke<T1>(object obj, string name, T1 arg1)
6369
throw exception.InnerException;
6470
}
6571
}
72+
73+
public static object InvokeStatic(Type type, string name, BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Static)
74+
{
75+
var methodInfo = type.GetMethods(flags)
76+
.Where(m => m.Name == name && m.GetParameters().Length == 0)
77+
.Single();
78+
return methodInfo.Invoke(null, new object[] { });
79+
}
80+
81+
public static object InvokeStatic<T1>(Type type, string name, T1 arg1)
82+
{
83+
var parameterTypes = new[] { typeof(T1) };
84+
var methodInfo = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Static)
85+
.Where(m => m.Name == name && m.GetParameters().Select(p => p.ParameterType).SequenceEqual(parameterTypes))
86+
.Single();
87+
return methodInfo.Invoke(null, new object[] { arg1 });
88+
}
6689

6790
public static void SetFieldValue(object obj, string name, object value, BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Instance)
6891
{

0 commit comments

Comments
 (0)