Skip to content

Commit 3df7de6

Browse files
author
Johnny Pham
authored
Give system key store providers precedence over instance-level providers (#1101)
1 parent a51a674 commit 3df7de6

File tree

6 files changed

+82
-22
lines changed

6 files changed

+82
-22
lines changed

doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2798,7 +2798,9 @@ Custom master key store providers can be registered with the driver at three dif
27982798
27992799
Once any key store provider is found at a registration level, the driver will **NOT** fall back to the other registrations to search for a provider. If providers are registered but the proper provider is not found at a level, an exception will be thrown containing only the registered providers in the registration that was checked.
28002800
2801-
The built-in column master key store providers that are available for the Windows Certificate Store, CNG Store and CSP are pre-registered. No providers should be registered on the connection or command instances if one of the built-in column master key store providers is needed.
2801+
The built-in column master key store providers that are available for the Windows Certificate Store, CNG Store and CSP are pre-registered.
2802+
2803+
This does shallow copying of the dictionary so that the app cannot alter the custom provider list once it has been set.
28022804
]]></format>
28032805
</remarks>
28042806
</RegisterColumnEncryptionKeyStoreProvidersOnCommand>

doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,7 +1026,7 @@ GO
10261026
<summary>
10271027
Registers the column encryption key store providers. This function should only be called once in an app. This does shallow copying of the dictionary so that the app cannot alter the custom provider list once it has been set.
10281028

1029-
The built-in column master key store providers that are available for the Windows Certificate Store, CNG Store and CSP are pre-registered. No providers should be registered on the connection or command instances if one of the built-in column master key store providers is needed.
1029+
The built-in column master key store providers that are available for the Windows Certificate Store, CNG Store and CSP are pre-registered.
10301030
</summary>
10311031
<remarks>
10321032
<format type="text/markdown"><![CDATA[
@@ -1085,7 +1085,9 @@ Custom master key store providers can be registered with the driver at three dif
10851085
10861086
Once any key store provider is found at a registration level, the driver will **NOT** fall back to the other registrations to search for a provider. If providers are registered but the proper provider is not found at a level, an exception will be thrown containing only the registered providers in the registration that was checked.
10871087
1088-
The built-in column master key store providers that are available for the Windows Certificate Store, CNG Store and CSP are pre-registered. No providers should be registered on the connection or command instances if one of the built-in column master key store providers is needed.
1088+
The built-in column master key store providers that are available for the Windows Certificate Store, CNG Store and CSP are pre-registered.
1089+
1090+
This does shallow copying of the dictionary so that the app cannot alter the custom provider list once it has been set.
10891091
]]></format>
10901092
</remarks>
10911093
</RegisterColumnEncryptionKeyStoreProvidersOnConnection>

src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,15 @@ private SqlConnection(SqlConnection connection)
212212
CacheConnectionStringProperties();
213213
}
214214

215+
internal static bool TryGetSystemColumnEncryptionKeyStoreProvider(string keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider)
216+
{
217+
return s_systemColumnEncryptionKeyStoreProviders.TryGetValue(keyStoreName, out provider);
218+
}
219+
215220
/// <summary>
216-
/// This function walks through both system and custom column encryption key store providers and returns an object if found.
221+
/// This function walks through both instance-level and global custom column encryption key store providers and returns an object if found.
217222
/// </summary>
218-
/// <param name="providerName">Provider Name to be searched in System Provider dictionary and Custom provider dictionary.</param>
223+
/// <param name="providerName">Provider Name to be searched for.</param>
219224
/// <param name="columnKeyStoreProvider">If the provider is found, initializes the corresponding SqlColumnEncryptionKeyStoreProvider instance.</param>
220225
/// <returns>true if the provider is found, else returns false</returns>
221226
internal bool TryGetColumnEncryptionKeyStoreProvider(string providerName, out SqlColumnEncryptionKeyStoreProvider columnKeyStoreProvider)
@@ -227,17 +232,12 @@ internal bool TryGetColumnEncryptionKeyStoreProvider(string providerName, out Sq
227232
return _customColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider);
228233
}
229234

230-
// Search in the sytem provider list.
231-
if (s_systemColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider))
232-
{
233-
return true;
234-
}
235-
236235
lock (s_globalCustomColumnEncryptionKeyProvidersLock)
237236
{
238237
// If custom provider is not set, then return false
239238
if (s_globalCustomColumnEncryptionKeyStoreProviders is null)
240239
{
240+
columnKeyStoreProvider = null;
241241
return false;
242242
}
243243

src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,16 @@ private static void ValidateCustomProviders(IDictionary<string, SqlColumnEncrypt
213213
}
214214
}
215215

216+
internal static bool TryGetSystemColumnEncryptionKeyStoreProvider(string keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider)
217+
{
218+
return s_systemColumnEncryptionKeyStoreProviders.TryGetValue(keyStoreName, out provider);
219+
}
220+
216221
/// <summary>
217-
/// This function walks through both system and custom column encryption key store providers and returns an object if found.
222+
/// This function walks through both instance-level and global custom column encryption key store providers and returns an object if found.
218223
/// </summary>
219-
/// <param name="providerName">Provider Name to be searched in System Provider diction and Custom provider dictionary.</param>
220-
/// <param name="columnKeyStoreProvider">If the provider is found, returns the corresponding SqlColumnEncryptionKeyStoreProvider instance.</param>
224+
/// <param name="providerName">Provider Name to be searched for.</param>
225+
/// <param name="columnKeyStoreProvider">If the provider is found, initializes the corresponding SqlColumnEncryptionKeyStoreProvider instance.</param>
221226
/// <returns>true if the provider is found, else returns false</returns>
222227
internal bool TryGetColumnEncryptionKeyStoreProvider(string providerName, out SqlColumnEncryptionKeyStoreProvider columnKeyStoreProvider)
223228
{
@@ -228,17 +233,12 @@ internal bool TryGetColumnEncryptionKeyStoreProvider(string providerName, out Sq
228233
return _customColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider);
229234
}
230235

231-
// Search in the system provider list.
232-
if (s_systemColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider))
233-
{
234-
return true;
235-
}
236-
237236
lock (s_globalCustomColumnEncryptionKeyProvidersLock)
238237
{
239238
// If custom provider is not set, then return false
240239
if (s_globalCustomColumnEncryptionKeyStoreProviders is null)
241240
{
241+
columnKeyStoreProvider = null;
242242
return false;
243243
}
244244

src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSecurityUtility.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using System.Reflection;
1010
using System.Security.Cryptography;
1111
using System.Text;
12+
using Microsoft.Data.Common;
1213

1314
namespace Microsoft.Data.SqlClient
1415
{
@@ -274,7 +275,7 @@ internal static void DecryptSymmetricKey(SqlTceCipherInfoEntry sqlTceCipherInfoE
274275
{
275276
try
276277
{
277-
sqlClientSymmetricKey = InstanceLevelProvidersAreRegistered(connection, command) ?
278+
sqlClientSymmetricKey = ShouldUseInstanceLevelProviderFlow(keyInfo.keyStoreName, connection, command) ?
278279
GetKeyFromLocalProviders(keyInfo, connection, command) :
279280
globalCekCache.GetKey(keyInfo, connection, command);
280281
encryptionkeyInfoChosen = keyInfo;
@@ -367,7 +368,7 @@ internal static void VerifyColumnMasterKeySignature(string keyStoreName, string
367368
GetListOfProviderNamesThatWereSearched(connection, command));
368369
}
369370

370-
if (InstanceLevelProvidersAreRegistered(connection, command))
371+
if (ShouldUseInstanceLevelProviderFlow(keyStoreName,connection, command))
371372
{
372373
isValidSignature = provider.VerifyColumnMasterKeyMetadata(keyPath, isEnclaveEnabled, CMKSignature);
373374
}
@@ -399,6 +400,15 @@ internal static void VerifyColumnMasterKeySignature(string keyStoreName, string
399400
}
400401
}
401402

403+
// Instance-level providers will be used if at least one is registered on a connection or command and
404+
// the required provider is not a system provider. System providers are pre-registered globally and
405+
// must use the global provider flow
406+
private static bool ShouldUseInstanceLevelProviderFlow(string keyStoreName, SqlConnection connection, SqlCommand command)
407+
{
408+
return InstanceLevelProvidersAreRegistered(connection, command) &&
409+
!keyStoreName.StartsWith(ADP.ColumnEncryptionSystemProviderNamePrefix);
410+
}
411+
402412
private static bool InstanceLevelProvidersAreRegistered(SqlConnection connection, SqlCommand command) =>
403413
connection.HasColumnEncryptionKeyStoreProvidersRegistered ||
404414
(command is not null && command.HasColumnEncryptionKeyStoreProvidersRegistered);
@@ -423,6 +433,11 @@ internal static bool TryGetColumnEncryptionKeyStoreProvider(string keyStoreName,
423433
{
424434
Debug.Assert(!string.IsNullOrWhiteSpace(keyStoreName), "Provider name is invalid");
425435

436+
if (SqlConnection.TryGetSystemColumnEncryptionKeyStoreProvider(keyStoreName, out provider))
437+
{
438+
return true;
439+
}
440+
426441
// command may be null because some callers do not have a command object, eg SqlBulkCopy
427442
if (command is not null && command.HasColumnEncryptionKeyStoreProvidersRegistered)
428443
{

src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2242,6 +2242,38 @@ public void TestCommandCustomKeyStoreProviderDuringAeQuery(string connectionStri
22422242
}
22432243
}
22442244

2245+
// On Windows, "_fixture" will be type SQLSetupStrategyCertStoreProvider
2246+
// On non-Windows, "_fixture" will be type SQLSetupStrategyAzureKeyVault
2247+
// Test will pass on both but only SQLSetupStrategyCertStoreProvider is a system provider
2248+
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))]
2249+
[ClassData(typeof(AEConnectionStringProvider))]
2250+
public void TestSystemProvidersHavePrecedenceOverInstanceLevelProviders(string connectionString)
2251+
{
2252+
Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new()
2253+
{
2254+
{
2255+
SqlColumnEncryptionAzureKeyVaultProvider.ProviderName,
2256+
new SqlColumnEncryptionAzureKeyVaultProvider(new SqlClientCustomTokenCredential())
2257+
}
2258+
};
2259+
2260+
using (SqlConnection connection = new(connectionString))
2261+
{
2262+
connection.Open();
2263+
using SqlCommand command = CreateCommandThatRequiresSystemKeyStoreProvider(connection);
2264+
connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(customKeyStoreProviders);
2265+
command.ExecuteReader();
2266+
}
2267+
2268+
using (SqlConnection connection = new(connectionString))
2269+
{
2270+
connection.Open();
2271+
using SqlCommand command = CreateCommandThatRequiresSystemKeyStoreProvider(connection);
2272+
command.RegisterColumnEncryptionKeyStoreProvidersOnCommand(customKeyStoreProviders);
2273+
command.ExecuteReader();
2274+
}
2275+
}
2276+
22452277
[ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE), nameof(DataTestUtility.EnclaveEnabled))]
22462278
[ClassData(typeof(AEConnectionStringProvider))]
22472279
public void TestRetryWhenAEParameterMetadataCacheIsStale(string connectionString)
@@ -2318,6 +2350,15 @@ private SqlCommand CreateCommandThatRequiresCustomKeyStoreProvider(SqlConnection
23182350
return command;
23192351
}
23202352

2353+
private SqlCommand CreateCommandThatRequiresSystemKeyStoreProvider(SqlConnection connection)
2354+
{
2355+
SqlCommand command = new(
2356+
$"SELECT * FROM [{_fixture.CustomKeyStoreProviderTestTable.Name}] WHERE FirstName = @firstName",
2357+
connection, null, SqlCommandColumnEncryptionSetting.Enabled);
2358+
command.Parameters.AddWithValue("firstName", "abc");
2359+
return command;
2360+
}
2361+
23212362
private void AssertExceptionCausedByFailureToDecrypt(Exception ex)
23222363
{
23232364
Assert.Contains(_failedToDecryptMessage, ex.Message);

0 commit comments

Comments
 (0)