diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Constants.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Constants.cs index f3ea843576..8c6c34f9e0 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Constants.cs +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Constants.cs @@ -24,7 +24,11 @@ internal static class Constants @"vault.azure.net", // default @"vault.azure.cn", // Azure China @"vault.usgovcloudapi.net", // US Government - @"vault.microsoftazure.de" // Azure Germany + @"vault.microsoftazure.de", // Azure Germany + @"managedhsm.azure.net", // public HSM vault + @"managedhsm.azure.cn", // Azure China HSM vault + @"managedhsm.usgovcloudapi.net", // US Government HSM vault + @"managedhsm.microsoftazure.de" // Azure Germany HSM vault }; /// diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs index 4c3c56cee2..2d01efb26d 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs @@ -101,16 +101,16 @@ public SqlColumnEncryptionAzureKeyVaultProvider(TokenCredential tokenCredential, /// and an array of trusted endpoints. /// /// Instance of an implementation of Token Credential that is capable of providing an OAuth Token - /// TrustedEndpoints are used to validate the master key path - public SqlColumnEncryptionAzureKeyVaultProvider(TokenCredential tokenCredential, string[] trustedEndPoints) + /// TrustedEndpoints are used to validate the master key path + public SqlColumnEncryptionAzureKeyVaultProvider(TokenCredential tokenCredential, string[] trustedEndpoints) { ValidateNotNull(tokenCredential, nameof(tokenCredential)); - ValidateNotNull(trustedEndPoints, nameof(trustedEndPoints)); - ValidateNotEmpty(trustedEndPoints, nameof(trustedEndPoints)); - ValidateNotNullOrWhitespaceForEach(trustedEndPoints, nameof(trustedEndPoints)); + ValidateNotNull(trustedEndpoints, nameof(trustedEndpoints)); + ValidateNotEmpty(trustedEndpoints, nameof(trustedEndpoints)); + ValidateNotNullOrWhitespaceForEach(trustedEndpoints, nameof(trustedEndpoints)); KeyCryptographer = new AzureSqlKeyCryptographer(tokenCredential); - TrustedEndPoints = trustedEndPoints; + TrustedEndPoints = trustedEndpoints; } #endregion @@ -242,7 +242,6 @@ public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string e { // Validate the input parameters ValidateNonEmptyAKVPath(masterKeyPath, isSystemOp: true); - ValidateNotNullOrWhitespace(encryptionAlgorithm, nameof(encryptionAlgorithm)); ValidateEncryptionAlgorithm(encryptionAlgorithm, isSystemOp: true); ValidateNotNull(columnEncryptionKey, nameof(columnEncryptionKey)); ValidateNotEmpty(columnEncryptionKey, nameof(columnEncryptionKey)); diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs index 97e15ebd56..29378fdac8 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs @@ -214,11 +214,11 @@ internal static string InvalidSignatureTemplate /// /// Looks up a localized string similar to Invalid trusted endpoint specified: '{0}'; a trusted endpoint must have a value.. /// - internal static string InvalidTrustedEndpointTemplate + internal static string NullOrWhitespaceForEach { get { - return ResourceManager.GetString("InvalidTrustedEndpointTemplate", resourceCulture); + return ResourceManager.GetString("NullOrWhitespaceForEach", resourceCulture); } } diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx index 7446876a52..5752720fb0 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx @@ -117,8 +117,8 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Invalid trusted endpoint specified: '{0}'; a trusted endpoint must have a value. + + One or more of the elements in {0} are null or empty or consist of only whitespace. CipherText length does not match the RSA key size. @@ -174,4 +174,4 @@ Internal error. Key encryption algorithm cannot be null. - + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Validator.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Validator.cs index cca06fdea6..f0611dd551 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Validator.cs +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Validator.cs @@ -24,7 +24,7 @@ internal static void ValidateNotNullOrWhitespace(string parameter, string name) { if (string.IsNullOrWhiteSpace(parameter)) { - throw new ArgumentException(name, Strings.NullOrWhitespaceArgument); + throw new ArgumentException(string.Format(Strings.NullOrWhitespaceArgument, name)); } } @@ -32,18 +32,15 @@ internal static void ValidateNotEmpty(IList parameter, string name) { if (parameter.Count == 0) { - throw new ArgumentException(name, Strings.EmptyArgumentInternal); + throw new ArgumentException(string.Format(Strings.EmptyArgumentInternal, name)); } } internal static void ValidateNotNullOrWhitespaceForEach(string[] parameters, string name) { - foreach (var parameter in parameters) + if (parameters.Any(s => string.IsNullOrWhiteSpace(s))) { - if (null == parameter) - { - throw new ArgumentException(parameter, Strings.InvalidTrustedEndpointTemplate); - } + throw new ArgumentException(string.Format(Strings.NullOrWhitespaceForEach, name)); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs index a9503d69de..80c087ee77 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs @@ -4,6 +4,7 @@ using System; using System.Security.Cryptography; +using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; using Xunit; @@ -45,7 +46,7 @@ public void NullEncryptionAlgorithm() Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, null, cek)); Assert.Matches($@"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex1.Message); Exception ex2 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, null, cek)); - Assert.Matches($@"Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex2.Message); + Assert.Matches($@"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex2.Message); } @@ -53,28 +54,28 @@ public void NullEncryptionAlgorithm() public void EmptyColumnEncryptionKey() { Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, new byte[] { })); - Assert.Matches($@"Empty column encryption key specified.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?", ex1.Message); + Assert.Matches($@"Internal error. Empty columnEncryptionKey specified.", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void NullColumnEncryptionKey() { Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, null)); - Assert.Matches($@"Column encryption key cannot be null.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?", ex1.Message); + Assert.Matches($@"Value cannot be null..\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void EmptyEncryptedColumnEncryptionKey() { Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, new byte[] { })); - Assert.Matches($@"Internal error. Empty encrypted column encryption key specified.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?", ex1.Message); + Assert.Matches($@"Internal error. Empty encryptedColumnEncryptionKey specified", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void NullEncryptedColumnEncryptionKey() { Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, null)); - Assert.Matches($@"Internal error. Encrypted column encryption key cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?", ex1.Message); + Assert.Matches($@"Value cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] @@ -152,18 +153,31 @@ public void NullAKVKeyPath() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void InvalidCertificatePath() { - string dummyPath = @"https://www.microsoft.com"; - string errorMessage = $@"Invalid Azure Key Vault key path specified: '{dummyPath}'. Valid trusted endpoints: vault.azure.net, vault.azure.cn, vault.usgovcloudapi.net, vault.microsoftazure.de.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + string dummyPathWithOnlyHost = @"https://www.microsoft.com"; + string invalidUrlErrorMessage = $@"Invalid url specified: '{dummyPathWithOnlyHost}'"; + string dummyPathWithInvalidKey = @"https://www.microsoft.vault.azure.com/keys/dummykey/dummykeyid"; + string invalidTrustedEndpointErrorMessage = $@"Invalid Azure Key Vault key path specified: '{dummyPathWithInvalidKey}'. +Valid trusted endpoints: vault.azure.net, vault.azure.cn, vault.usgovcloudapi.net, vault.microsoftazure.de, managedhsm.azure.net, +managedhsm.azure.cn, managedhsm.usgovcloudapi.net, managedhsm.microsoftazure.de.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + + Exception ex = Assert.Throws( + () => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, cek)); + Assert.Matches(invalidUrlErrorMessage, ex.Message); + + ex = Assert.Throws( + () => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, cek)); + Assert.Matches(invalidTrustedEndpointErrorMessage, ex.Message); + + ex = Assert.Throws( + () => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, encryptedCek)); + Assert.Matches(invalidUrlErrorMessage, ex.Message); - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPath, MasterKeyEncAlgo, cek)); - Assert.Matches(errorMessage, ex1.Message); - - Exception ex2 = Assert.Throws( - () => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPath, MasterKeyEncAlgo, encryptedCek)); - Assert.Matches(errorMessage, ex2.Message); + ex = Assert.Throws( + () => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, encryptedCek)); + Assert.Matches(invalidTrustedEndpointErrorMessage, ex.Message); } - [InlineData(true)] + [InlineData(true)] [InlineData(false)] [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void AkvStoreProviderVerifyFunctionWithInvalidSignature(bool fEnclaveEnabled) @@ -206,5 +220,19 @@ public void AkvStoreProviderVerifyFunctionWithInvalidSignature(bool fEnclaveEnab tamperedCmkSignature[startingByteIndex + randomIndexInCipherText[0]] = cmkSignature[startingByteIndex + randomIndexInCipherText[0]]; } } + + [InlineData(new object[] { new string[] { null } })] + [InlineData(new object[] { new string[] { "" } })] + [InlineData(new object[] { new string[] { " " } })] + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] + public void InvalidTrustedEndpoints(string[] trustedEndpoints) + { + Exception ex = Assert.Throws(() => + { + SqlColumnEncryptionAzureKeyVaultProvider azureKeyProvider = new SqlColumnEncryptionAzureKeyVaultProvider( + new SqlClientCustomTokenCredential(), trustedEndpoints); + }); + Assert.Matches("One or more of the elements in trustedEndpoints are null or empty or consist of only whitespace.", ex.Message); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 617f905ff7..06c1b2794e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -107,7 +107,7 @@ static DataTestUtility() } AKVOriginalUrl = c.AzureKeyVaultURL; - if (!string.IsNullOrEmpty(AKVOriginalUrl) && Uri.TryCreate(AKVOriginalUrl, UriKind.Absolute, out Uri AKVBaseUri)) + if (!string.IsNullOrEmpty(AKVOriginalUrl) && Uri.TryCreate(AKVOriginalUrl, UriKind.Absolute, out AKVBaseUri)) { AKVBaseUri = new Uri(AKVBaseUri, "/"); AKVBaseUrl = AKVBaseUri.AbsoluteUri;