From bfdbd76ad37b21da8c6e9c55e452714fb9e01f9c Mon Sep 17 00:00:00 2001 From: Johnny Pham Date: Thu, 13 Jan 2022 16:46:48 -0800 Subject: [PATCH] Test | Add lock when using ClearSqlConnectionGlobalProvidersk (#1461) --- .../ExceptionRegisterKeyStoreProvider.cs | 25 ++--- .../ExceptionsAlgorithmErrors.cs | 74 +++++++------- ...ncryptionCertificateStoreProviderShould.cs | 97 +++++++++---------- .../AlwaysEncryptedTests/Utility.cs | 4 +- 4 files changed, 101 insertions(+), 99 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionRegisterKeyStoreProvider.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionRegisterKeyStoreProvider.cs index dfec766011..4c62013843 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionRegisterKeyStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionRegisterKeyStoreProvider.cs @@ -85,21 +85,24 @@ public void TestEmptyProviderName() [Fact] public void TestCanSetGlobalProvidersOnlyOnce() { - Utility.ClearSqlConnectionGlobalProviders(); + lock (Utility.ClearSqlConnectionGlobalProvidersLock) + { + Utility.ClearSqlConnectionGlobalProviders(); - IDictionary customProviders = - new Dictionary() - { + IDictionary customProviders = + new Dictionary() + { { DummyKeyStoreProvider.Name, new DummyKeyStoreProvider() } - }; - SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders); + }; + SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders); - InvalidOperationException e = Assert.Throws( - () => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); - string expectedMessage = SystemDataResourceManager.Instance.TCE_CanOnlyCallOnce; - Assert.Contains(expectedMessage, e.Message); + InvalidOperationException e = Assert.Throws( + () => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); + string expectedMessage = SystemDataResourceManager.Instance.TCE_CanOnlyCallOnce; + Assert.Contains(expectedMessage, e.Message); - Utility.ClearSqlConnectionGlobalProviders(); + Utility.ClearSqlConnectionGlobalProviders(); + } } [Fact] diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs index 7395816fb1..e2d8e02b0b 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs @@ -82,7 +82,7 @@ public void TestInvalidCipherText() [PlatformSpecific(TestPlatforms.Windows)] public void TestInvalidAlgorithmVersion() { - string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_InvalidAlgorithmVersion, + string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_InvalidAlgorithmVersion, 40, "01"); byte[] plainText = Encoding.Unicode.GetBytes("Hello World"); byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); @@ -112,7 +112,7 @@ public void TestInvalidAuthenticationTag() [PlatformSpecific(TestPlatforms.Windows)] public void TestNullColumnEncryptionAlgorithm() { - string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_NullColumnEncryptionAlgorithm, + string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_NullColumnEncryptionAlgorithm, "'AEAD_AES_256_CBC_HMAC_SHA256'"); Object cipherMD = GetSqlCipherMetadata(0, 0, null, 1, 0x01); AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); @@ -148,24 +148,27 @@ public void TestUnknownEncryptionAlgorithmId() [PlatformSpecific(TestPlatforms.Windows)] public void TestUnknownCustomKeyStoreProvider() { - // Clear out the existing providers (to ensure test reliability) - ClearSqlConnectionGlobalProviders(); - - const string invalidProviderName = "Dummy_Provider"; - string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnrecognizedKeyStoreProviderName, - invalidProviderName, "'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'", ""); - Object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x03); - AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, invalidProviderName, "RSA_OAEP"); - byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + lock (Utility.ClearSqlConnectionGlobalProvidersLock) + { + // Clear out the existing providers (to ensure test reliability) + ClearSqlConnectionGlobalProviders(); - Exception decryptEx = Assert.Throws(() => DecryptWithKey(plainText, cipherMD)); - Assert.Contains(expectedMessage, decryptEx.InnerException.Message); + const string invalidProviderName = "Dummy_Provider"; + string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnrecognizedKeyStoreProviderName, + invalidProviderName, "'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'", ""); + Object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x03); + AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, invalidProviderName, "RSA_OAEP"); + byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); + byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); - Exception encryptEx = Assert.Throws(() => EncryptWithKey(plainText, cipherMD)); - Assert.Contains(expectedMessage, encryptEx.InnerException.Message); + Exception decryptEx = Assert.Throws(() => DecryptWithKey(plainText, cipherMD)); + Assert.Contains(expectedMessage, decryptEx.InnerException.Message); + + Exception encryptEx = Assert.Throws(() => EncryptWithKey(plainText, cipherMD)); + Assert.Contains(expectedMessage, encryptEx.InnerException.Message); - ClearSqlConnectionGlobalProviders(); + ClearSqlConnectionGlobalProviders(); + } } [Fact] @@ -173,7 +176,7 @@ public void TestUnknownCustomKeyStoreProvider() public void TestTceUnknownEncryptionAlgorithm() { const string unknownEncryptionAlgorithm = "Dummy"; - string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnknownColumnEncryptionAlgorithm, + string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnknownColumnEncryptionAlgorithm, unknownEncryptionAlgorithm, "'AEAD_AES_256_CBC_HMAC_SHA256'"); Object cipherMD = GetSqlCipherMetadata(0, 0, "Dummy", 1, 0x01); AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); @@ -193,7 +196,7 @@ public void TestExceptionsFromCertStore() { byte[] corruptedCek = GenerateInvalidEncryptedCek(CertFixture.cek, ECEKCorruption.SIGNATURE); - string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_KeyDecryptionFailedCertStore, + string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_KeyDecryptionFailedCertStore, "MSSQL_CERTIFICATE_STORE", BitConverter.ToString(corruptedCek, corruptedCek.Length - 10, 10)); Object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x01); @@ -209,27 +212,30 @@ public void TestExceptionsFromCertStore() [PlatformSpecific(TestPlatforms.Windows)] public void TestExceptionsFromCustomKeyStore() { - string expectedMessage = "Failed to decrypt a column encryption key"; + lock (Utility.ClearSqlConnectionGlobalProvidersLock) + { + string expectedMessage = "Failed to decrypt a column encryption key"; - // Clear out the existing providers (to ensure test reliability) - ClearSqlConnectionGlobalProviders(); + // Clear out the existing providers (to ensure test reliability) + ClearSqlConnectionGlobalProviders(); - IDictionary customProviders = new Dictionary(); - customProviders.Add(DummyKeyStoreProvider.Name, new DummyKeyStoreProvider()); - SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders); + IDictionary customProviders = new Dictionary(); + customProviders.Add(DummyKeyStoreProvider.Name, new DummyKeyStoreProvider()); + SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders); - object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "DummyProvider", "DummyAlgo"); - byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x01); + AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "DummyProvider", "DummyAlgo"); + byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); + byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); - Exception decryptEx = Assert.Throws(() => DecryptWithKey(cipherText, cipherMD)); - Assert.Contains(expectedMessage, decryptEx.InnerException.Message); + Exception decryptEx = Assert.Throws(() => DecryptWithKey(cipherText, cipherMD)); + Assert.Contains(expectedMessage, decryptEx.InnerException.Message); - Exception encryptEx = Assert.Throws(() => EncryptWithKey(cipherText, cipherMD)); - Assert.Contains(expectedMessage, encryptEx.InnerException.Message); + Exception encryptEx = Assert.Throws(() => EncryptWithKey(cipherText, cipherMD)); + Assert.Contains(expectedMessage, encryptEx.InnerException.Message); - ClearSqlConnectionGlobalProviders(); + ClearSqlConnectionGlobalProviders(); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs index 54dd6bc6be..b0c6297cda 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs @@ -92,11 +92,6 @@ public class SqlColumnEncryptionCertificateStoreProviderWindowsShould : IClassFi /// private const int CipherTextStartIndex = IVStartIndex + IVLengthInBytes; - /// - /// SetCustomColumnEncryptionKeyStoreProvider can be called only once in a process. To workaround that, we use this flag. - /// - private static bool s_testCustomEncryptioKeyStoreProviderExecutedOnce = false; - [Theory] [InvalidDecryptionParameters] [PlatformSpecific(TestPlatforms.Windows)] @@ -326,55 +321,51 @@ public void TestAeadEncryptionReversal(string dataType, object data, Utility.CCo [PlatformSpecific(TestPlatforms.Windows)] public void TestCustomKeyProviderListSetter() { - // SqlConnection.RegisterColumnEncryptionKeyStoreProviders can be called only once in a process. - // This is a workaround to ensure re-runnability of the test. - if (s_testCustomEncryptioKeyStoreProviderExecutedOnce) + lock (Utility.ClearSqlConnectionGlobalProvidersLock) { - return; + string expectedMessage1 = "Column encryption key store provider dictionary cannot be null. Expecting a non-null value."; + // Verify that we are able to set it to null. + ArgumentException e1 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(null)); + Assert.Contains(expectedMessage1, e1.Message); + + // A dictionary holding custom providers. + IDictionary customProviders = new Dictionary(); + customProviders.Add(new KeyValuePair(@"DummyProvider", new DummyKeyStoreProvider())); + + // Verify that setting a provider in the list with null value throws an exception. + customProviders.Add(new KeyValuePair(@"CustomProvider", null)); + string expectedMessage2 = "Null reference specified for key store provider 'CustomProvider'. Expecting a non-null value."; + ArgumentNullException e2 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); + Assert.Contains(expectedMessage2, e2.Message); + customProviders.Remove(@"CustomProvider"); + + // Verify that setting a provider in the list with an empty provider name throws an exception. + customProviders.Add(new KeyValuePair(@"", new DummyKeyStoreProvider())); + string expectedMessage3 = "Invalid key store provider name specified. Key store provider names cannot be null or empty"; + ArgumentNullException e3 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); + Assert.Contains(expectedMessage3, e3.Message); + + customProviders.Remove(@""); + + // Verify that setting a provider in the list with name that starts with 'MSSQL_' throws an exception. + customProviders.Add(new KeyValuePair(@"MSSQL_MyStore", new SqlColumnEncryptionCertificateStoreProvider())); + string expectedMessage4 = "Invalid key store provider name 'MSSQL_MyStore'. 'MSSQL_' prefix is reserved for system key store providers."; + ArgumentException e4 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); + Assert.Contains(expectedMessage4, e4.Message); + + customProviders.Remove(@"MSSQL_MyStore"); + + // Verify that setting a provider in the list with name that starts with 'MSSQL_' but different case throws an exception. + customProviders.Add(new KeyValuePair(@"MsSqL_MyStore", new SqlColumnEncryptionCertificateStoreProvider())); + string expectedMessage5 = "Invalid key store provider name 'MsSqL_MyStore'. 'MSSQL_' prefix is reserved for system key store providers."; + ArgumentException e5 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); + Assert.Contains(expectedMessage5, e5.Message); + + customProviders.Remove(@"MsSqL_MyStore"); + + // Clear any providers set by other tests. + Utility.ClearSqlConnectionGlobalProviders(); } - - string expectedMessage1 = "Column encryption key store provider dictionary cannot be null. Expecting a non-null value."; - // Verify that we are able to set it to null. - ArgumentException e1 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(null)); - Assert.Contains(expectedMessage1, e1.Message); - - // A dictionary holding custom providers. - IDictionary customProviders = new Dictionary(); - customProviders.Add(new KeyValuePair(@"DummyProvider", new DummyKeyStoreProvider())); - - // Verify that setting a provider in the list with null value throws an exception. - customProviders.Add(new KeyValuePair(@"CustomProvider", null)); - string expectedMessage2 = "Null reference specified for key store provider 'CustomProvider'. Expecting a non-null value."; - ArgumentNullException e2 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); - Assert.Contains(expectedMessage2, e2.Message); - customProviders.Remove(@"CustomProvider"); - - // Verify that setting a provider in the list with an empty provider name throws an exception. - customProviders.Add(new KeyValuePair(@"", new DummyKeyStoreProvider())); - string expectedMessage3 = "Invalid key store provider name specified. Key store provider names cannot be null or empty"; - ArgumentNullException e3 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); - Assert.Contains(expectedMessage3, e3.Message); - - customProviders.Remove(@""); - - // Verify that setting a provider in the list with name that starts with 'MSSQL_' throws an exception. - customProviders.Add(new KeyValuePair(@"MSSQL_MyStore", new SqlColumnEncryptionCertificateStoreProvider())); - string expectedMessage4 = "Invalid key store provider name 'MSSQL_MyStore'. 'MSSQL_' prefix is reserved for system key store providers."; - ArgumentException e4 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); - Assert.Contains(expectedMessage4, e4.Message); - - customProviders.Remove(@"MSSQL_MyStore"); - - // Verify that setting a provider in the list with name that starts with 'MSSQL_' but different case throws an exception. - customProviders.Add(new KeyValuePair(@"MsSqL_MyStore", new SqlColumnEncryptionCertificateStoreProvider())); - string expectedMessage5 = "Invalid key store provider name 'MsSqL_MyStore'. 'MSSQL_' prefix is reserved for system key store providers."; - ArgumentException e5 = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); - Assert.Contains(expectedMessage5, e5.Message); - - customProviders.Remove(@"MsSqL_MyStore"); - - // Clear any providers set by other tests. - Utility.ClearSqlConnectionGlobalProviders(); } [Theory] @@ -502,7 +493,7 @@ public class CEKEncryptionReversalParameters : DataAttribute { public override IEnumerable GetData(MethodInfo testMethod) { - yield return new object[2] { StoreLocation.CurrentUser , CurrentUserMyPathPrefix }; + yield return new object[2] { StoreLocation.CurrentUser, CurrentUserMyPathPrefix }; // use localmachine cert path only when current user is Admin. if (CertificateFixture.IsAdmin) { diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs index 6bc51d6acd..9f858a0fca 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs @@ -402,9 +402,11 @@ internal static string GetHexString(byte[] input, bool addLeadingZeroX = false) return str.ToString(); } + internal static object ClearSqlConnectionGlobalProvidersLock = new(); + /// /// Through reflection, clear the static provider list set on SqlConnection. - /// Note- This API doesn't use locks for synchronization. + /// Note- Any test using this method should be wrapped in a lock statement using ClearSqlConnectionGlobalProvidersLock /// internal static void ClearSqlConnectionGlobalProviders() {