diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index d944cd2046..5a7d0080e1 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -21,12 +21,12 @@ Once the environment is setup properly, execute the desired set of commands belo ```bash > msbuild /p:Configuration=Release -# Builds the driver in 'Release' Configuration. +# Builds the driver in 'Release' Configuration for `AnyCPU` platform. ``` ```bash > msbuild /p:Platform=Win32 -# Builds the .NET Framework (NetFx) driver for Win32 (x86) platform on Windows. +# Builds the .NET Framework (NetFx) driver for Win32 (x86) platform on Windows in 'Debug' Configuration. ``` ```bash @@ -64,35 +64,37 @@ Once the environment is setup properly, execute the desired set of commands belo ```bash > msbuild /t:BuildTestsNetCore -# Build the tests for the .NET Core driver. Default .NET Core version is 2.1. +# Build the tests for the .NET Core driver in 'Debug' Configuration. Default .NET Core version is 2.1. ``` ```bash > msbuild /t:BuildTestsNetFx -# Build the tests for the .NET Framework (NetFx) driver. Default .NET Framework version is 4.6. +# Build the tests for the .NET Framework (NetFx) driver in 'Debug' Configuration. Default .NET Framework version is 4.6.1. ``` ## Run Functional Tests -Windows (`netfx x86`): -```bash -> dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj" /p:Platform="Win32" /p:Configuration="Release" /p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" -``` +- Windows (`netfx x86`): + ```bash + > dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj" /p:Platform="Win32" /p:Configuration="Release" /p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" + ``` -Windows (`netfx x64`): -```bash -> dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj" /p:Platform="x64" /p:Configuration="Release" /p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" -``` +- Windows (`netfx x64`): + ```bash + > dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj" /p:Platform="x64" /p:Configuration="Release" /p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" + ``` -Windows (`netcoreapp`): -```bash -> dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj" /p:Platform="AnyCPU" /p:Configuration="Release" /p:TestTargetOS="Windowsnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests" -``` +- AnyCPU: -Unix (`netcoreapp`): -```bash -> dotnet test "src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj" /p:Platform="AnyCPU" /p:Configuration="Release" /p:TestTargetOS="Unixnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonlinuxtests&category!=nonuaptests" -``` + Windows (`netcoreapp`): + ```bash + > dotnet test "src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj" /p:Platform="AnyCPU" /p:Configuration="Release" /p:TestTargetOS="Windowsnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests" + ``` + + Unix (`netcoreapp`): + ```bash + > dotnet test "src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj" /p:Platform="AnyCPU" /p:Configuration="Release" /p:TestTargetOS="Unixnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonlinuxtests&category!=nonuaptests" + ``` ## Run Manual Tests @@ -102,46 +104,53 @@ Manual Tests require the below setup to run: * Databases "NORTHWIND" and "UdtTestDb" present in SQL Server, created using SQL scripts [createNorthwindDb.sql](tools/testsql/createNorthwindDb.sql) and [createUdtTestDb.sql](tools/testsql/createUdtTestDb.sql). To setup an Azure Database with "NORTHWIND" tables, use SQL Script: [createNorthwindAzureDb.sql](tools/testsql/createNorthwindAzureDb.sql). * Make a copy of the configuration file [config.default.json](src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json) and rename it to `config.json`. Update the values in `config.json`: -|Property|Description|Value| -|------|--------|-------------------| -|TCPConnectionString | Connection String for a TCP enabled SQL Server instance. | `Server={servername};Database={Database_Name};Trusted_Connection=True;`
OR `Data Source={servername};Initial Catalog={Database_Name};Integrated Security=True;`| -|NPConnectionString | Connection String for a Named Pipes enabled SQL Server instance.| `Server=\\{servername}\pipe\sql\query;Database={Database_Name};Trusted_Connection=True;`
OR
`Data Source=np:{servername};Initial Catalog={Database_Name};Integrated Security=True;`| -|TCPConnectionStringHGSVBS | (Optional) Connection String for a TCP enabled SQL Server with Host Guardian Service (HGS) attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = HGS; Enclave Attestation Url = {AttestationURL};`| -|AADAuthorityURL | (Optional) Identifies the OAuth2 authority resource for `Server` specified in `AADPasswordConnectionString` | `https://login.windows.net/`, where `` is the tenant ID of the Azure Active Directory (Azure AD) tenant | -|AADPasswordConnectionString | (Optional) Connection String for testing Azure Active Directory Password Authentication. | `Data Source={server.database.windows.net}; Initial Catalog={Azure_DB_Name};Authentication=Active Directory Password; User ID={AAD_User}; Password={AAD_User_Password};`| -|AADSecurePrincipalId | (Optional) The Application Id of a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Application ID} | -|AADSecurePrincipalSecret | (Optional) A Secret defined for a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Secret} | -|AzureKeyVaultURL | (Optional) Azure Key Vault Identifier URL | `https://{keyvaultname}.vault.azure.net/` | -|AzureKeyVaultTenantId | (Optional) The Azure Active Directory tenant (directory) Id of the service principal. | _{Tenant ID of Active Directory}_ | -|AzureKeyVaultClientId | (Optional) "Application (client) ID" of an Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL`. Requires the key permissions Get, List, Import, Decrypt, Encrypt, Unwrap, Wrap, Verify, and Sign. | _{Client Application ID}_ | -|AzureKeyVaultClientSecret | (Optional) "Client Secret" of the Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL` | _{Client Application Secret}_ | -|SupportsLocalDb | (Optional) Whether or not a LocalDb instance of SQL Server is installed on the machine running the tests. |`true` OR `false`| -|SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`| -|SupportsFileStream | (Optional) Whether or not FileStream is enabled on SQL Server| `true` OR `false`| -|UseManagedSNIOnWindows | (Optional) Enables testing with Managed SNI on Windows| `true` OR `false`| -|IsAzureSynpase | (Optional) When set to 'true', test suite runs compatible tests for Azure Synapse/Parallel Data Warehouse. | `true` OR `false`| - -Commands to run tests: - -Windows (`netfx x86`): -```bash -> dotnet test "src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj" /p:Platform="Win32" /p:Configuration="Release" /p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" -``` + |Property|Description|Value| + |------|--------|-------------------| + |TCPConnectionString | Connection String for a TCP enabled SQL Server instance. | `Server={servername};Database={Database_Name};Trusted_Connection=True;`
OR `Data Source={servername};Initial Catalog={Database_Name};Integrated Security=True;`| + |NPConnectionString | Connection String for a Named Pipes enabled SQL Server instance.| `Server=\\{servername}\pipe\sql\query;Database={Database_Name};Trusted_Connection=True;`
OR
`Data Source=np:{servername};Initial Catalog={Database_Name};Integrated Security=True;`| + |TCPConnectionStringHGSVBS | (Optional) Connection String for a TCP enabled SQL Server with Host Guardian Service (HGS) attestation protocol configuration. | `Server=tcp:{servername}; Database={Database_Name}; UID={UID}; PWD={PWD}; Attestation Protocol = HGS; Enclave Attestation Url = {AttestationURL};`| + |AADAuthorityURL | (Optional) Identifies the OAuth2 authority resource for `Server` specified in `AADPasswordConnectionString` | `https://login.windows.net/`, where `` is the tenant ID of the Azure Active Directory (Azure AD) tenant | + |AADPasswordConnectionString | (Optional) Connection String for testing Azure Active Directory Password Authentication. | `Data Source={server.database.windows.net}; Initial Catalog={Azure_DB_Name};Authentication=Active Directory Password; User ID={AAD_User}; Password={AAD_User_Password};`| + |AADSecurePrincipalId | (Optional) The Application Id of a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Application ID} | + |AADSecurePrincipalSecret | (Optional) A Secret defined for a registered application which has been granted permission to the database defined in the AADPasswordConnectionString. | {Secret} | + |AzureKeyVaultURL | (Optional) Azure Key Vault Identifier URL | `https://{keyvaultname}.vault.azure.net/` | + |AzureKeyVaultTenantId | (Optional) The Azure Active Directory tenant (directory) Id of the service principal. | _{Tenant ID of Active Directory}_ | + |AzureKeyVaultClientId | (Optional) "Application (client) ID" of an Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL`. Requires the key permissions Get, List, Import, Decrypt, Encrypt, Unwrap, Wrap, Verify, and Sign. | _{Client Application ID}_ | + |AzureKeyVaultClientSecret | (Optional) "Client Secret" of the Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL` | _{Client Application Secret}_ | + |SupportsLocalDb | (Optional) Whether or not a LocalDb instance of SQL Server is installed on the machine running the tests. |`true` OR `false`| + |SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`| + |SupportsFileStream | (Optional) Whether or not FileStream is enabled on SQL Server| `true` OR `false`| + |UseManagedSNIOnWindows | (Optional) Enables testing with Managed SNI on Windows| `true` OR `false`| + |IsAzureSynpase | (Optional) When set to 'true', test suite runs compatible tests for Azure Synapse/Parallel Data Warehouse. | `true` OR `false`| + +### Commands to run Manual Tests: + + - Windows (`netfx x86`): + ```bash + > dotnet test "src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj" /p:Platform="Win32" /p:Configuration="Release" /p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" + ``` -Windows (`netfx x64`): -```bash -> dotnet test "src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj" /p:Platform="x64" /p:Configuration="Release" /p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" -``` + - Windows (`netfx x64`): + ```bash + > dotnet test "src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj" /p:Platform="x64" /p:Configuration="Release" /p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" + ``` -Windows (`netcoreapp`): -```bash -> dotnet test "src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj" /p:Platform="AnyCPU" /p:Configuration="Release" /p:TestTargetOS="Windowsnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests" -``` + - AnyCPU: -Unix (`netcoreapp`): -```bash -> dotnet test "src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj" /p:Platform="AnyCPU" /p:Configuration="Release" /p:TestTargetOS="Unixnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonlinuxtests&category!=nonuaptests" -``` + Windows (`netfx`): + ```bash + > dotnet test "src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj" /p:Platform="AnyCPU" /p:Configuration="Release" /p:TestTargetOS="Windowsnetfx" --no-build -v n --filter "category!=nonnetfxtests&category!=failing&category!=nonwindowstests" + ``` + + Windows (`netcoreapp`): + ```bash + > dotnet test "src\Microsoft.Data.SqlClient\tests\ManualTests\Microsoft.Data.SqlClient.ManualTesting.Tests.csproj" /p:Platform="AnyCPU" /p:Configuration="Release" /p:TestTargetOS="Windowsnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonwindowstests" + ``` + + Unix (`netcoreapp`): + ```bash + > dotnet test "src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj" /p:Platform="AnyCPU" /p:Configuration="Release" /p:TestTargetOS="Unixnetcoreapp" --no-build -v n --filter "category!=nonnetcoreapptests&category!=failing&category!=nonlinuxtests&category!=nonuaptests" + ``` ## Run A Single Test ```bash @@ -242,6 +251,12 @@ To use this feature, you must enable the following AppContext switch at applicat **"Switch.Microsoft.Data.SqlClient.EnableRetryLogic"** +## Enabling row version null behavior + +`SqlDataReader` returns a `DBNull` value instead of an empty `byte[]`. To enable the legacy behavior, you must enable the following AppContext switch on application startup: + +**"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"** + ## Debugging SqlClient on Linux from Windows For enhanced developer experience, we support debugging SqlClient on Linux from Windows, using the project "**Microsoft.Data.SqlClient.DockerLinuxTest**" that requires "Container Tools" to be enabled in Visual Studio. You may import configuration: [VS19Components.vsconfig](./tools/vsconfig/VS19Components.vsconfig) if not enabled already. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ef104f47e..e59feab7c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +## [Preview Release 3.0.0-preview2.21106.5] - 2021-04-16 + +### Breaking Changes over preview release v3.0.0-preview1 +- `User Id` connection property now requires `Client Id` instead of `Object Id` for **User-Assigned Managed Identity** [#1010](https://github.com/dotnet/SqlClient/pull/1010) +- `SqlDataReader` now returns a `DBNull` value instead of an empty `byte[]`. Legacy behavior can be enabled by setting `AppContext` switch **Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior** [#998](https://github.com/dotnet/SqlClient/pull/998) + +### Added +- **Microsoft.Data.SqlClient** now depends on **Azure.Identity** library to acquire a token for "Active Directory Managed Identity/MSI" and "Active Directory Service Principal" authentication modes. [#1010](https://github.com/dotnet/SqlClient/pull/1010) +- Upgraded Native SNI dependency to **v3.0.0-preview1** along with enhanced event tracing support [#1006](https://github.com/dotnet/SqlClient/pull/1006) + +### Fixed +- Fixed wrong data blended with transactions in .NET Core by marking a connection as doomed if the transaction completes or aborts while there is an open result set[#1023](https://github.com/dotnet/SqlClient/pull/1023) +- Fixed derived parameters containing incorrect typename [#1020](https://github.com/dotnet/SqlClient/pull/1020) +- Fixed server connection leak possibilities when an exception occurs in pooling layer [#890](https://github.com/dotnet/SqlClient/pull/890) +- Fixed IP connection resolving logic in .NET Core [#1016](https://github.com/dotnet/SqlClient/pull/1016) [#1031](https://github.com/dotnet/SqlClient/pull/1031) + +### Changed +- Performance improvements in `SqlDateTime` to `DateTime` internal conversion method [#912](https://github.com/dotnet/SqlClient/pull/912) +- Improved memory allocation by avoiding unnecessary context switching [1008](https://github.com/dotnet/SqlClient/pull/1008) +- Updated `Microsoft.Identity.Client` version from **4.21.1** to **4.22.0** [#1036](https://github.com/dotnet/SqlClient/pull/1036) +- Various performance improvements [#963](https://github.com/dotnet/SqlClient/pull/963) [#996](https://github.com/dotnet/SqlClient/pull/996) [#1004](https://github.com/dotnet/SqlClient/pull/1004) [#1012](https://github.com/dotnet/SqlClient/pull/1012) [#1017](https://github.com/dotnet/SqlClient/pull/1017) +- Event source tracing improvements [#1018](https://github.com/dotnet/SqlClient/pull/1018) +- Changes to share common files between NetFx and NetCore source code [#871](https://github.com/dotnet/SqlClient/pull/871) [#887](https://github.com/dotnet/SqlClient/pull/887) + ## [Preview Release 3.0.0-preview1.21075.2] - 2021-03-15 ### Breaking Changes over stable release v2.1 @@ -27,7 +51,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed missing error messages in Managed SNI [#882](https://github.com/dotnet/SqlClient/pull/882) - Fixed event source trace string issue [#940](https://github.com/dotnet/SqlClient/pull/940) -### Changes +### Changed - Changed App Context switch `MakeReadAsyncBlocking` default to `false` [#937](https://github.com/dotnet/SqlClient/pull/937) - Replaced usage of `BinaryFormatter` with `DataContractSerializer` [#869](https://github.com/dotnet/SqlClient/pull/869) - Prohibited `DtdProcessing` on `XmlTextReader` instance in .NET Core [#884](https://github.com/dotnet/SqlClient/pull/884) @@ -80,7 +104,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed Bulk Copy Async deadlock issues with custom `IDataReader` when using `SqlDataReader` internally [#779](https://github.com/dotnet/SqlClient/pull/779) - Fixed a serialization issue with `SqlException` in .NET Core [#780](https://github.com/dotnet/SqlClient/pull/780) -### Changes +### Changed - Updated versions of `Microsoft.IdentityModel` package dependencies [#794](https://github.com/dotnet/SqlClient/pull/794) @@ -96,7 +120,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed unobserved exception issue when a timeout occurs before a faulted task completes with an exception [#688](https://github.com/dotnet/SqlClient/pull/688) [#773](https://github.com/dotnet/SqlClient/pull/773) - Fixed an issue where driver continues to prompt for credentials when using Azure Active Directory authentication [#770](https://github.com/dotnet/SqlClient/pull/770) -### Changes +### Changed - Updated `Microsoft.Data.SqlClient.SNI` (.NET Framework dependency) and `Microsoft.Data.SqlClient.SNI.runtime` (.NET Core/Standard dependency) version to `v2.1.1` and removed symbols from `Microsoft.Data.SqlClient.SNI.runtime`, which are now published to Microsoft Symbols Server [#764](https://github.com/dotnet/SqlClient/pull/764) - Updated `Microsoft.Identity.Client` dependency version to `v4.21.1` [#765](https://github.com/dotnet/SqlClient/pull/765) - Performance improvements when establishing an encrypted channel by removing sync over async method calls [#541](https://github.com/dotnet/SqlClient/pull/541) @@ -136,7 +160,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed SPN generation issue when no port is provided [#629](https://github.com/dotnet/SqlClient/pull/629) - Fixed missing null checks for `SqlErrors` in `SqlException` for .NET Framework implementation [#698](https://github.com/dotnet/SqlClient/pull/698) -### Changes +### Changed - Performance improvements by fixing unnecessary allocations with EventSource implementation [#684](https://github.com/dotnet/SqlClient/pull/684) - Reverted changes to return empty DataTable from GetSchemaTable to return null as before. [#696](https://github.com/dotnet/SqlClient/pull/696) - Removed multiple `CacheConnectionStringProperties` calls when setting `ConnectionString` properties [#683](https://github.com/dotnet/SqlClient/pull/683) @@ -164,7 +188,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed Object null reference issue when failover partner is set [#588](https://github.com/dotnet/SqlClient/pull/588) - Fixed `applicationintent` connection string property issue [#585](https://github.com/dotnet/SqlClient/pull/585) -### Changes +### Changed - Raise warning message when insecure TLS protocols are in use [#591](https://github.com/dotnet/SqlClient/pull/591) ### Breaking Changes @@ -184,7 +208,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed unsafe cast in `SqlException` for `SerializationEntry.Value` - Fixed null reference exceptions in `SqlDelegatedTransaction` methods [#563](https://github.com/dotnet/SqlClient/pull/563) -### Changes +### Changed - Standardized connection string properties for enhanced user experience [#534](https://github.com/dotnet/SqlClient/pull/534) - Improved performance by reducing eventsource tracing related to allocations from TVP write methods [#557](https://github.com/dotnet/SqlClient/pull/557) [#564](https://github.com/dotnet/SqlClient/pull/564) @@ -213,7 +237,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed wrong application domain selected when starting `SqlDependencyListener` [#410](https://github.com/dotnet/SqlClient/pull/410) - Added missing refs for `RowCopied` property in `SqlBulkCopy` [#508](https://github.com/dotnet/SqlClient/pull/508) -### Changes +### Changed - Improved performance by removing unwanted method calls in Event Source tracing [#506](https://github.com/dotnet/SqlClient/pull/506) - Removed Diagnostic Source and Configuration Manager dependencies from .NET Standard implementation [#535](https://github.com/dotnet/SqlClient/pull/535) - Removed redundant calls to `DbConnectionPoolKey.GetType()` [#512](https://github.com/dotnet/SqlClient/pull/512) @@ -252,7 +276,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed concurrent connection speed issues when connecting with Azure Active Directory Authentication modes in .NET Core [#466](https://github.com/dotnet/SqlClient/pull/466) - Fixed issues with `Password` persistence in Connection String [#453](https://github.com/dotnet/SqlClient/pull/453) -### Changes +### Changed - Updated all driver assemblies to be CLS Compliant [#396](https://github.com/dotnet/SqlClient/pull/396) - Updated Bulk Copy error messages to also include Column, Row and non-encrypted Data information [#437](https://github.com/dotnet/SqlClient/pull/437) - Updated error messages for "Always Encrypted - Secure Enclaves" to handle 'Attestation Protocol' and fixed typos [#421](https://github.com/dotnet/SqlClient/pull/421) [#397](https://github.com/dotnet/SqlClient/pull/397) @@ -288,7 +312,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed `ConnectionTime` and `ClientConnectionId` reported by `SqlStatistics` when connection is closed [#341](https://github.com/dotnet/SqlClient/pull/341) - Fixed deadlock issues by reverting async changes to `SNIPacket` [#349](https://github.com/dotnet/SqlClient/pull/349) -### Changes +### Changed - Improved performance of Managed SNI by removing double fetch of domain name [#366](https://github.com/dotnet/SqlClient/pull/366) - Improved performance of Async Method Allocations in Managed SNI [#328](https://github.com/dotnet/SqlClient/pull/328) - Improved performance of Managed SNI by enhancing utilization of resources [#173](https://github.com/dotnet/SqlClient/pull/173) - Ported [dotnet/corefx#35363](https://github.com/dotnet/corefx/pull/35363) and [dotnet/corefx#40732](https://github.com/dotnet/corefx/pull/40732) @@ -310,7 +334,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Fixed driver behavior to abort connection when encountering `SqlException` on `SqlTransaction.Commit` [#299](https://github.com/dotnet/SqlClient/pull/299) - Fixed driver behavior to not throw exception on invalid *app.config* files [#319](https://github.com/dotnet/SqlClient/pull/319) -### Changes +### Changed - Improved async read performance by adding multi-packet target buffer caching [#285](https://github.com/dotnet/SqlClient/pull/285) - Improved performance of `TdsParserStateObject` and `SqlDataReader` snapshot mechanisms [#198](https://github.com/dotnet/SqlClient/pull/198) - Updated `SqlDataReader.Close` documentation [#314](https://github.com/dotnet/SqlClient/pull/314) @@ -332,7 +356,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Exception message grammar: "An SqlParameter [...] is not contained by this `SqlParameterCollection`" [#159](https://github.com/dotnet/SqlClient/issues/159) - Fixing incorrect event id and opcode for the `SqlEventSource` [#241](https://github.com/dotnet/SqlClient/pull/241) -### Changes +### Changed - Update dependency to Microsoft.Data.SqlClient.SNI v1.1.0 [#276](https://github.com/dotnet/SqlClient/pull/276) - Correct timeout remarks for async command methods [#264](https://github.com/dotnet/SqlClient/pull/264) - Improve `SqlBulkCopy` truncation error message [#256](https://github.com/dotnet/SqlClient/issues/256) @@ -347,7 +371,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Added `SqlFileStream` support for .NET Framework with `Microsoft.Data.SqlTypes.SqlFileStream` class introduced. [#210](https://github.com/dotnet/SqlClient/pull/210) - Added support for Visual Studio Intellisense with XML Documentation. [#210](https://github.com/dotnet/SqlClient/pull/210) -### Changes +### Changed - Synchronized ref definitions with driver classes. [#180](https://github.com/dotnet/SqlClient/pull/180) - Updated `SNINativeMethodWrapper` to provide the underlying error in the inner exception when we fail to load SNI.dll. [#225](https://github.com/dotnet/SqlClient/pull/225) - Added .editorconfig file and set formatting rules. [#193](https://github.com/dotnet/SqlClient/pull/193) diff --git a/build.proj b/build.proj index 428008a4a8..9d59894bf0 100644 --- a/build.proj +++ b/build.proj @@ -48,6 +48,7 @@ + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index 4cc4448f8b..7eee99980d 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -1052,6 +1052,24 @@ GO This function was called more than once. + + Dictionary of custom column encryption key providers + Registers the encryption key store providers on the instance. If this function has been called, any providers registered using the static methods will be ignored. This function can be called more than once. This does shallow copying of the dictionary so that the app cannot alter the custom provider list once it has been set. + + A null dictionary was provided. + + -or- + + A string key in the dictionary was null or empty. + + -or- + + An EncryptionKeyStoreProvider value in the dictionary was null. + + + A string key in the dictionary started with "MSSQL_". This prefix is reserved for system providers. + + Gets or sets a value that specifies the diff --git a/release-notes/3.0/3.0.0-preview2.md b/release-notes/3.0/3.0.0-preview2.md new file mode 100644 index 0000000000..df52852bd7 --- /dev/null +++ b/release-notes/3.0/3.0.0-preview2.md @@ -0,0 +1,116 @@ +# Release Notes + +## Microsoft.Data.SqlClient 3.0.0-preview2.21106.5 released 16 April 2021 + +This update brings the below changes over the previous release: + +### Breaking Changes over preview release V3.0.0-preview1 +- `User Id` connection property now requires `Client Id` instead of `Object Id` for **User-Assigned Managed Identity** [#1010](https://github.com/dotnet/SqlClient/pull/1010) [Read more](#azure-identity-dependency-introduction) +- `SqlDataReader` now returns a `DBNull` value instead of an empty `byte[]`. Legacy behavior can be enabled by setting `AppContext` switch **Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior** [#998](https://github.com/dotnet/SqlClient/pull/998) [Read more](#enabling-row-version-null-behavior) + +### Added +**Microsoft.Data.SqlClient** now depends on **Azure.Identity** library to acquire a token for "Active Directory Managed Identity/MSI" and "Active Directory Service Principal" authentication modes. [#1010](https://github.com/dotnet/SqlClient/pull/1010) [Read more](#azure-identity-dependency-introduction) +- Upgraded Native SNI dependency to **v3.0.0-preview1** along with enhanced event tracing support [#1006](https://github.com/dotnet/SqlClient/pull/1006) [Read more](#event-tracing-improvements-in-sni.dll) + +### Fixed +- Fixed wrong data blended with transactions in .NET Core by marking a connection as doomed if the transaction completes or aborts while there is an open result set [#1023](https://github.com/dotnet/SqlClient/pull/1023) +- Fixed derived parameters containing incorrect typename [#1020](https://github.com/dotnet/SqlClient/pull/1020) +- Fixed server connection leak possibilities when an exception occurs in pooling layer [#890](https://github.com/dotnet/SqlClient/pull/890) +- Fixed IP connection resolving logic in .NET Core [#1016](https://github.com/dotnet/SqlClient/pull/1016) [#1031](https://github.com/dotnet/SqlClient/pull/1031) + +### Changed +- Performance improvements in `SqlDateTime` to `DateTime` internal conversion method [#912](https://github.com/dotnet/SqlClient/pull/912) +- Improved memory allocation by avoiding unnecessary context switching [1008](https://github.com/dotnet/SqlClient/pull/1008) +- Updated `Microsoft.Identity.Client` version from **4.21.1** to **4.22.0** [#1036](https://github.com/dotnet/SqlClient/pull/1036) +- Various performance improvements [#963](https://github.com/dotnet/SqlClient/pull/963) [#996](https://github.com/dotnet/SqlClient/pull/996) [#1004](https://github.com/dotnet/SqlClient/pull/1004) [#1012](https://github.com/dotnet/SqlClient/pull/1012) [#1017](https://github.com/dotnet/SqlClient/pull/1017) +- Event source tracing improvements [#1018](https://github.com/dotnet/SqlClient/pull/1018) +- Changes to share common files between NetFx and NetCore source code [#871](https://github.com/dotnet/SqlClient/pull/871) [#887](https://github.com/dotnet/SqlClient/pull/887) + +### Azure Identity dependency introduction +**Microsoft.Data.SqlClient** now depends on the **Azure.Identity** library to acquire tokens for "Active Directory Managed Identity/MSI" and "Active Directory Service Principal" authentication modes. This change brings the following changes to the public surface area: + +- **Breaking Change** + The "User Id" connection property now requires "Client Id" instead of "Object Id" for "User-Assigned Managed Identity". +- **Public API** + New read-only public property: `SqlAuthenticationParameters.ConnectionTimeout` +- **Dependency** + Azure.Identity v1.3.0 + +### Event tracing improvements in SNI.dll +`Microsoft.Data.SqlClient.SNI` (.NET Framework dependency) and `Microsoft.Data.SqlClient.SNI.runtime` (.NET Core/Standard dependency) versions have been updated to `v3.0.0-preview1.21104.2`. Event tracing in SNI.dll will no longer be enabled through a client application. Subscribing a session to the **Microsoft.Data.SqlClient.EventSource** provider through tools like xperf or perfview will be sufficient. + +### Enabling row version null behavior +`SqlDataReader` returns a `DBNull` value instead of an empty `byte[]`. To enable the legacy behavior, you must enable the following AppContext switch on application startup: +**"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"** + +## Target Platform Support + +- .NET Framework 4.6.1+ (Windows x86, Windows x64) +- .NET Core 2.1+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework 4.6.1 + +- Microsoft.Data.SqlClient.SNI 3.0.0-preview1.21104.2 +- Azure.Identity 1.3.0 +- Microsoft.Identity.Client 4.22.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0 +- Microsoft.IdentityModel.JsonWebTokens 6.8.0 + +#### .NET Core 2.1 + +- Microsoft.Data.SqlClient.SNI.runtime 3.0.0-preview1.21104.2 +- Microsoft.Win32.Registry 4.7.0 +- System.Security.Principal.Windows 4.7.0 +- System.Text.Encoding.CodePages 4.7.0 +- System.Diagnostics.DiagnosticSource 4.7.0 +- System.Configuration.ConfigurationManager 4.7.0 +- System.Runtime.Caching 4.7.0 +- Azure.Identity 1.3.0 +- Microsoft.Identity.Client 4.22.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0 +- Microsoft.IdentityModel.JsonWebTokens 6.8.0 + +#### .NET Core 3.1 + +- Microsoft.Data.SqlClient.SNI.runtime 3.0.0-preview1.21104.2 +- Microsoft.Win32.Registry 4.7.0 +- System.Security.Principal.Windows 4.7.0 +- System.Text.Encoding.CodePages 4.7.0 +- System.Diagnostics.DiagnosticSource 4.7.0 +- System.Configuration.ConfigurationManager 4.7.0 +- System.Runtime.Caching 4.7.0 +- Azure.Identity 1.3.0 +- Microsoft.Identity.Client 4.22.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0 +- Microsoft.IdentityModel.JsonWebTokens 6.8.0 + +#### .NET Standard 2.0 + +- Microsoft.Data.SqlClient.SNI.runtime 3.0.0-preview1.21104.2 +- Microsoft.Win32.Registry 4.7.0 +- System.Buffers 4.5.1 +- System.Memory 4.5.4 +- System.Security.Principal.Windows 4.7.0 +- System.Text.Encoding.CodePages 4.7.0 +- System.Runtime.Caching 4.7.0 +- Azure.Identity 1.3.0 +- Microsoft.Identity.Client 4.22.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0 +- Microsoft.IdentityModel.JsonWebTokens 6.8.0 + +#### .NET Standard 2.1 + +- Microsoft.Data.SqlClient.SNI.runtime 3.0.0-preview1.21104.2 +- Microsoft.Win32.Registry 4.7.0 +- System.Buffers 4.5.1 +- System.Memory 4.5.4 +- System.Security.Principal.Windows 4.7.0 +- System.Text.Encoding.CodePages 4.7.0 +- System.Runtime.Caching 4.7.0 +- Azure.Identity 1.3.0 +- Microsoft.Identity.Client 4.22.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.8.0 +- Microsoft.IdentityModel.JsonWebTokens 6.8.0 diff --git a/release-notes/3.0/3.0.md b/release-notes/3.0/3.0.md index 8dfa0288b2..7dff6ce7de 100644 --- a/release-notes/3.0/3.0.md +++ b/release-notes/3.0/3.0.md @@ -4,4 +4,5 @@ The following Microsoft.Data.SqlClient 3.0 preview releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2021/04/15 | 3.0.0-preview2.21106.5 | [release notes](3.0.0-preview2.md) | | 2021/03/15 | 3.0.0-preview1.21075.2 | [release notes](3.0.0-preview1.md) | diff --git a/release-notes/3.0/README.md b/release-notes/3.0/README.md index 8dfa0288b2..7dff6ce7de 100644 --- a/release-notes/3.0/README.md +++ b/release-notes/3.0/README.md @@ -4,4 +4,5 @@ The following Microsoft.Data.SqlClient 3.0 preview releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2021/04/15 | 3.0.0-preview2.21106.5 | [release notes](3.0.0-preview2.md) | | 2021/03/15 | 3.0.0-preview1.21075.2 | [release notes](3.0.0-preview1.md) | diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index 127672b0f9..a03e196464 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -172,6 +172,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.Te EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.ExtUtilities", "Microsoft.Data.SqlClient\tests\tools\Microsoft.Data.SqlClient.ExtUtilities\Microsoft.Data.SqlClient.ExtUtilities.csproj", "{E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomRetryLogicProvider", "Microsoft.Data.SqlClient\tests\CustomConfigurableRetryLogic\CustomRetryLogicProvider.csproj", "{B499E477-C9B1-4087-A5CF-5C762D90E433}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1159,6 +1161,7 @@ Global {E7336BFB-8521-423A-A140-3123F9065C5D} = {0CC4817A-12F3-4357-912C-09315FAAD008} {89D6D382-9B36-43C9-A912-03802FDA8E36} = {0CC4817A-12F3-4357-912C-09315FAAD008} {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B} = {0CC4817A-12F3-4357-912C-09315FAAD008} + {B499E477-C9B1-4087-A5CF-5C762D90E433} = {0CC4817A-12F3-4357-912C-09315FAAD008} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431} diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 1d2f3a3b64..17bc6c59c4 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -701,7 +701,9 @@ public SqlConnection(string connectionString, Microsoft.Data.SqlClient.SqlCreden public static System.Collections.Generic.IDictionary> ColumnEncryptionTrustedMasterKeyPaths { get { throw null; } } /// public static void RegisterColumnEncryptionKeyStoreProviders(System.Collections.Generic.IDictionary customProviders) { } - /// + /// + public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(System.Collections.Generic.IDictionary customProviders) { } + /// [System.ComponentModel.BrowsableAttribute(false)] [System.ComponentModel.DesignerSerializationVisibilityAttribute(0)] public string AccessToken { get { throw null; } set { } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 4546d7d1ab..a309305dc3 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -214,6 +214,9 @@ Microsoft\Data\SqlClient\SqlConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\SqlCredential.cs + Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs @@ -318,7 +321,12 @@ - + + Microsoft\Data\SqlClient\EnclaveDelegate.NotSupported.cs + + + Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.NotSupported.cs + @@ -335,9 +343,7 @@ Microsoft\Data\SqlClient\AlwaysEncryptedEnclaveProviderUtils.cs - - Microsoft\Data\SqlClient\AlwaysEncryptedKeyConverter.cs - + Microsoft\Data\SqlClient\EnclaveProviderBase.cs @@ -345,13 +351,21 @@ Microsoft\Data\SqlClient\EnclaveSessionCache.cs - - - + + Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.Crypto.cs + + + Microsoft\Data\SqlClient\EnclaveDelegate.Crypto.cs + + + Microsoft\Data\SqlClient\AzureAttestationBasedEnclaveProvider.cs + Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProvider.cs - + + Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProviderBase.cs + @@ -484,7 +498,9 @@ - + + Microsoft\Data\SqlClient\SqlUdtInfo.cs + @@ -519,11 +535,9 @@ - - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.CrossPlatform.cs similarity index 72% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.cs rename to src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.CrossPlatform.cs index 6ccbd67fb6..8b3def875c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.CrossPlatform.cs @@ -5,12 +5,56 @@ using System; using System.Diagnostics; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; namespace Microsoft.Data.SqlClient { - // Contains methods to convert cryptography keys between different formats. - internal sealed class KeyConverter - { + internal sealed partial class KeyConverter + { + // Magic numbers identifying blob types + // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/cba27df5-4880-4f95-a879-783f8657e53b + private readonly struct KeyBlobMagicNumber + { + internal static readonly byte[] ECDHPublicP384 = new byte[] { 0x45, 0x43, 0x4b, 0x33 }; + } + + // The ECC public key blob is structured as follows: + // BCRYPT_ECCKEY_BLOB header + // byte[KeySize] X + // byte[KeySize] Y + private readonly struct ECCPublicKeyBlob + { + // Size of an ECC public key blob + internal const int Size = 104; + // Size of the BCRYPT_ECCKEY_BLOB header + internal const int HeaderSize = 8; + // Size of each coordinate + internal const int KeySize = (Size - HeaderSize) / 2; + } + + // Serializes an ECDiffieHellmanPublicKey to an ECC public key blob + // "ECDiffieHellmanPublicKey.ToByteArray() doesn't have a (standards-)defined export + // format. The version used by ECDiffieHellmanPublicKeyCng is Windows-specific" + // from https://github.com/dotnet/runtime/issues/27276 + // => ECDiffieHellmanPublicKey.ToByteArray() is not supported in Unix + internal static byte[] GetECDiffieHellmanPublicKeyBlob(ECDiffieHellman ecDiffieHellman) + { + byte[] keyBlob = new byte[ECCPublicKeyBlob.Size]; + + // Set magic number + Buffer.BlockCopy(KeyBlobMagicNumber.ECDHPublicP384, 0, keyBlob, 0, 4); + // Set key size + keyBlob[4] = (byte)ECCPublicKeyBlob.KeySize; + + ECPoint ecPoint = ecDiffieHellman.PublicKey.ExportParameters().Q; + Debug.Assert(ecPoint.X.Length == ECCPublicKeyBlob.KeySize && ecPoint.Y.Length == ECCPublicKeyBlob.KeySize, + $"ECDH public key was not the expected length. Actual (X): {ecPoint.X.Length}. Actual (Y): {ecPoint.Y.Length} Expected: {ECCPublicKeyBlob.Size}"); + // Copy x and y coordinates to key blob + Buffer.BlockCopy(ecPoint.X, 0, keyBlob, ECCPublicKeyBlob.HeaderSize, ECCPublicKeyBlob.KeySize); + Buffer.BlockCopy(ecPoint.Y, 0, keyBlob, ECCPublicKeyBlob.HeaderSize + ECCPublicKeyBlob.KeySize, ECCPublicKeyBlob.KeySize); + return keyBlob; + } + // The RSA public key blob is structured as follows: // BCRYPT_RSAKEY_BLOB header // byte[ExponentSize] publicExponent @@ -29,59 +73,32 @@ private readonly struct RSAPublicKeyBlob internal const int ModulusOffset = HeaderSize; } - // Extracts the public key's modulus and exponent from an RSA public key blob - // and returns an RSAParameters object - internal static RSAParameters RSAPublicKeyBlobToParams(byte[] keyBlob) + internal static RSA CreateRSAFromPublicKeyBlob(byte[] keyBlob) { - Debug.Assert(keyBlob.Length == RSAPublicKeyBlob.Size, - $"RSA public key blob was not the expected length. Actual: {keyBlob.Length}. Expected: {RSAPublicKeyBlob.Size}"); + Debug.Assert(keyBlob.Length == RSAPublicKeyBlob.Size, $"RSA public key blob was not the expected length. Actual: {keyBlob.Length}. Expected: {RSAPublicKeyBlob.Size}"); byte[] exponent = new byte[RSAPublicKeyBlob.ExponentSize]; byte[] modulus = new byte[RSAPublicKeyBlob.ModulusSize]; Buffer.BlockCopy(keyBlob, RSAPublicKeyBlob.ExponentOffset, exponent, 0, RSAPublicKeyBlob.ExponentSize); Buffer.BlockCopy(keyBlob, RSAPublicKeyBlob.ModulusOffset, modulus, 0, RSAPublicKeyBlob.ModulusSize); - - return new RSAParameters() + var rsaParameters = new RSAParameters() { Exponent = exponent, Modulus = modulus }; + return RSA.Create(rsaParameters); } - // The ECC public key blob is structured as follows: - // BCRYPT_ECCKEY_BLOB header - // byte[KeySize] X - // byte[KeySize] Y - private readonly struct ECCPublicKeyBlob - { - // Size of an ECC public key blob - internal const int Size = 104; - // Size of the BCRYPT_ECCKEY_BLOB header - internal const int HeaderSize = 8; - // Size of each coordinate - internal const int KeySize = (Size - HeaderSize) / 2; - } - - // Magic numbers identifying blob types - // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wcce/cba27df5-4880-4f95-a879-783f8657e53b - private readonly struct KeyBlobMagicNumber - { - internal static readonly byte[] ECDHPublicP384 = new byte[] { 0x45, 0x43, 0x4b, 0x33 }; - } - - // Extracts the public key's X and Y coordinates from an ECC public key blob - // and returns an ECParameters object - internal static ECParameters ECCPublicKeyBlobToParams(byte[] keyBlob) + internal static ECDiffieHellman CreateECDiffieHellmanFromPublicKeyBlob(byte[] keyBlob) { - Debug.Assert(keyBlob.Length == ECCPublicKeyBlob.Size, - $"ECC public key blob was not the expected length. Actual: {keyBlob.Length}. Expected: {ECCPublicKeyBlob.Size}"); + Debug.Assert(keyBlob.Length == ECCPublicKeyBlob.Size, $"ECC public key blob was not the expected length. Actual: {keyBlob.Length}. Expected: {ECCPublicKeyBlob.Size}"); byte[] x = new byte[ECCPublicKeyBlob.KeySize]; byte[] y = new byte[ECCPublicKeyBlob.KeySize]; Buffer.BlockCopy(keyBlob, ECCPublicKeyBlob.HeaderSize, x, 0, ECCPublicKeyBlob.KeySize); Buffer.BlockCopy(keyBlob, ECCPublicKeyBlob.HeaderSize + ECCPublicKeyBlob.KeySize, y, 0, ECCPublicKeyBlob.KeySize); - return new ECParameters + var parameters = new ECParameters { Curve = ECCurve.NamedCurves.nistP384, Q = new ECPoint @@ -90,29 +107,29 @@ internal static ECParameters ECCPublicKeyBlobToParams(byte[] keyBlob) Y = y }, }; + + return ECDiffieHellman.Create(parameters); } - // Serializes an ECDiffieHellmanPublicKey to an ECC public key blob - // "ECDiffieHellmanPublicKey.ToByteArray() doesn't have a (standards-)defined export - // format. The version used by ECDiffieHellmanPublicKeyCng is Windows-specific" - // from https://github.com/dotnet/runtime/issues/27276 - // => ECDiffieHellmanPublicKey.ToByteArray() is not supported in Unix - internal static byte[] ECDHPublicKeyToECCKeyBlob(ECDiffieHellmanPublicKey publicKey) + internal static ECDiffieHellman CreateECDiffieHellman(int keySize) { - byte[] keyBlob = new byte[ECCPublicKeyBlob.Size]; + // platform agnostic creates a key of the correct size but does not + // set the key derivation type or algorithm, these must be set by calling + // DeriveKeyFromHash later in DeriveKey + ECDiffieHellman clientDHKey = ECDiffieHellman.Create(); + clientDHKey.KeySize = keySize; + return clientDHKey; + } - // Set magic number - Buffer.BlockCopy(KeyBlobMagicNumber.ECDHPublicP384, 0, keyBlob, 0, 4); - // Set key size - keyBlob[4] = (byte)ECCPublicKeyBlob.KeySize; + internal static byte[] DeriveKey(ECDiffieHellman ecd, ECDiffieHellmanPublicKey publicKey) + { + // see notes in CreateECDDiffieHellman + return ecd.DeriveKeyFromHash(publicKey, HashAlgorithmName.SHA256); + } - ECPoint ecPoint = publicKey.ExportParameters().Q; - Debug.Assert(ecPoint.X.Length == ECCPublicKeyBlob.KeySize && ecPoint.Y.Length == ECCPublicKeyBlob.KeySize, - $"ECDH public key was not the expected length. Actual (X): {ecPoint.X.Length}. Actual (Y): {ecPoint.Y.Length} Expected: {ECCPublicKeyBlob.Size}"); - // Copy x and y coordinates to key blob - Buffer.BlockCopy(ecPoint.X, 0, keyBlob, ECCPublicKeyBlob.HeaderSize, ECCPublicKeyBlob.KeySize); - Buffer.BlockCopy(ecPoint.Y, 0, keyBlob, ECCPublicKeyBlob.HeaderSize + ECCPublicKeyBlob.KeySize, ECCPublicKeyBlob.KeySize); - return keyBlob; + internal static RSA GetRSAFromCertificate(X509Certificate2 certificate) + { + return certificate.GetRSAPublicKey(); } } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs index f8f7ca338e..f229bba751 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -91,6 +91,11 @@ private static readonly Dictionary /// private static IReadOnlyDictionary s_globalCustomColumnEncryptionKeyStoreProviders; + /// + /// Per-connection custom providers. It can be provided by the user and can be set more than once. + /// + private IReadOnlyDictionary _customColumnEncryptionKeyStoreProviders; + /// /// Dictionary object holding trusted key paths for various SQL Servers. /// Key to the dictionary is a SQL Server Name @@ -234,6 +239,13 @@ internal static bool TryGetColumnEncryptionKeyStoreProvider(string providerName, return true; } + // instance-level custom provider cache takes precedence over global cache + if (connection._customColumnEncryptionKeyStoreProviders != null && + connection._customColumnEncryptionKeyStoreProviders.Count > 0) + { + return connection._customColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider); + } + lock (s_globalCustomColumnEncryptionKeyProvidersLock) { // If custom provider is not set, then return false @@ -264,6 +276,11 @@ internal static List GetColumnEncryptionSystemKeyStoreProviders() /// Combined list of provider names internal static List GetColumnEncryptionCustomKeyStoreProviders(SqlConnection connection) { + if (connection._customColumnEncryptionKeyStoreProviders != null && + connection._customColumnEncryptionKeyStoreProviders.Count > 0) + { + return connection._customColumnEncryptionKeyStoreProviders.Keys.ToList(); + } if (s_globalCustomColumnEncryptionKeyStoreProviders != null) { return s_globalCustomColumnEncryptionKeyStoreProviders.Keys.ToList(); @@ -306,6 +323,24 @@ public static void RegisterColumnEncryptionKeyStoreProviders(IDictionary + public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(IDictionary customProviders) + { + ValidateCustomProviders(customProviders); + + // Create a temporary dictionary and then add items from the provided dictionary. + // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs + // in the provided customerProviders dictionary. + Dictionary customColumnEncryptionKeyStoreProviders = + new Dictionary(customProviders, StringComparer.OrdinalIgnoreCase); + + // Set the dictionary to the ReadOnly dictionary. + // This method can be called more than once. Re-registering a new collection will replace the + // old collection of providers. + _customColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders; + } + private static void ValidateCustomProviders(IDictionary customProviders) { // Throw when the provided dictionary is null. diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs index 13257cb827..e861c7ec31 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs @@ -324,24 +324,21 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment) RuntimeHelpers.PrepareConstrainedRegions(); try { - // If the connection is doomed, we can be certain that the - // transaction will eventually be rolled back, and we shouldn't - // attempt to commit it. - if (connection.IsConnectionDoomed) + lock (connection) { - lock (connection) + // If the connection is doomed, we can be certain that the + // transaction will eventually be rolled back or has already been aborted externally, and we shouldn't + // attempt to commit it. + if (connection.IsConnectionDoomed) { _active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done. _connection = null; - } - enlistment.Aborted(SQL.ConnectionDoomed()); - } - else - { - Exception commitException; - lock (connection) + enlistment.Aborted(SQL.ConnectionDoomed()); + } + else { + Exception commitException; try { // Now that we've acquired the lock, make sure we still have valid state for this operation. @@ -367,40 +364,40 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment) commitException = e; connection.DoomThisConnection(); } - } - if (commitException != null) - { - // connection.ExecuteTransaction failed with exception - if (_internalTransaction.IsCommitted) - { - // Even though we got an exception, the transaction - // was committed by the server. - enlistment.Committed(); - } - else if (_internalTransaction.IsAborted) + if (commitException != null) { - // The transaction was aborted, report that to - // SysTx. - enlistment.Aborted(commitException); + // connection.ExecuteTransaction failed with exception + if (_internalTransaction.IsCommitted) + { + // Even though we got an exception, the transaction + // was committed by the server. + enlistment.Committed(); + } + else if (_internalTransaction.IsAborted) + { + // The transaction was aborted, report that to + // SysTx. + enlistment.Aborted(commitException); + } + else + { + // The transaction is still active, we cannot + // know the state of the transaction. + enlistment.InDoubt(commitException); + } + + // We eat the exception. This is called on the SysTx + // thread, not the applications thread. If we don't + // eat the exception an UnhandledException will occur, + // causing the process to FailFast. } - else + + connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); + if (commitException == null) { - // The transaction is still active, we cannot - // know the state of the transaction. - enlistment.InDoubt(commitException); + // connection.ExecuteTransaction succeeded + enlistment.Committed(); } - - // We eat the exception. This is called on the SysTx - // thread, not the applications thread. If we don't - // eat the exception an UnhandledException will occur, - // causing the process to FailFast. - } - - connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); - if (commitException == null) - { - // connection.ExecuteTransaction succeeded - enlistment.Committed(); } } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs deleted file mode 100644 index 739187a7e9..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NetCoreApp.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Security.Cryptography; - -namespace Microsoft.Data.SqlClient -{ - /// - internal partial class SqlEnclaveAttestationParameters - { - private static readonly string _clientDiffieHellmanKeyName = "ClientDiffieHellmanKey"; - private static readonly string _inputName = "input"; - private static readonly string _className = "EnclaveAttestationParameters"; - - /// - internal ECDiffieHellman ClientDiffieHellmanKey { get; } - - /// - internal SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellman clientDiffieHellmanKey) - { - _input = input ?? throw SQL.NullArgumentInConstructorInternal(_inputName, _className); - Protocol = protocol; - ClientDiffieHellmanKey = clientDiffieHellmanKey ?? throw SQL.NullArgumentInConstructorInternal(_clientDiffieHellmanKeyName, _className); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs deleted file mode 100644 index 25f2737c70..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Data.SqlClient -{ - /// - internal partial class SqlEnclaveAttestationParameters - { - private readonly byte[] _input = null; - - /// - internal int Protocol { get; } - - /// - internal byte[] GetInput() - { - return Clone(_input); - } - - /// - /// Deep copy the array into a new array - /// - /// - /// - private byte[] Clone(byte[] arrayToClone) - { - - if (null == arrayToClone) - { - return null; - } - - byte[] returnValue = new byte[arrayToClone.Length]; - - for (int i = 0; i < arrayToClone.Length; i++) - { - returnValue[i] = arrayToClone[i]; - } - - return returnValue; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index e591a41300..bd7a46fa07 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -850,6 +850,8 @@ public void Open(SqlConnectionOverrides overrides) { } public override System.Threading.Tasks.Task OpenAsync(System.Threading.CancellationToken cancellationToken) { throw null; } /// public static void RegisterColumnEncryptionKeyStoreProviders(System.Collections.Generic.IDictionary customProviders) { } + /// + public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(System.Collections.Generic.IDictionary customProviders) { } /// public void ResetStatistics() { } /// diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index d80ca43a2d..914fe96939 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -151,9 +151,16 @@ Microsoft\Data\SqlClient\AlwaysEncryptedEnclaveProviderUtils.cs + + + Microsoft\Data\SqlClient\AzureAttestationBasedEnclaveProvider.cs + Microsoft\Data\SqlClient\EnclaveDelegate.cs + + Microsoft\Data\SqlClient\EnclaveDelegate.Crypto.cs + Microsoft\Data\SqlClient\EnclavePackage.cs @@ -283,6 +290,12 @@ Microsoft\Data\SqlClient\SqlConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\SqlEnclaveAttestationParameters.Crypto.cs + + + Microsoft\Data\SqlClient\SqlEnclaveSession.cs + Microsoft\Data\SqlClient\SqlInfoMessageEventHandler.cs @@ -319,6 +332,9 @@ Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProvider.cs + + Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProviderBase.cs + Microsoft\Data\SqlTypes\SqlTypeWorkarounds.cs @@ -403,15 +419,12 @@ - - - @@ -441,7 +454,9 @@ - + + Microsoft\Data\SqlClient\SqlCredential.cs + @@ -449,10 +464,6 @@ - - - Microsoft\Data\SqlClient\SqlEnclaveSession.cs - @@ -474,7 +485,9 @@ - + + Microsoft\Data\SqlClient\SqlUdtInfo.cs + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.Cng.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.Cng.cs new file mode 100644 index 0000000000..f0d9943ca8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AlwaysEncryptedKeyConverter.Cng.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.Data.SqlClient +{ + internal sealed partial class KeyConverter + { + internal static RSA CreateRSAFromPublicKeyBlob(byte[] keyBlob) + { + CngKey key = CngKey.Import(keyBlob, CngKeyBlobFormat.GenericPublicBlob); + return new RSACng(key); + } + + internal static ECDiffieHellman CreateECDiffieHellmanFromPublicKeyBlob(byte[] keyBlob) + { + CngKey key = CngKey.Import(keyBlob, CngKeyBlobFormat.GenericPublicBlob); + return new ECDiffieHellmanCng(key); + } + + internal static ECDiffieHellman CreateECDiffieHellman(int keySize) + { + // Cng sets the key size and hash algorithm at creation time and these + // parameters are then used later when DeriveKeyMaterial is called + ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(keySize); + clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; + clientDHKey.HashAlgorithm = CngAlgorithm.Sha256; + return clientDHKey; + } + + public static byte[] GetECDiffieHellmanPublicKeyBlob(ECDiffieHellman ecDiffieHellman) + { + if (ecDiffieHellman is ECDiffieHellmanCng cng) + { + return cng.Key.Export(CngKeyBlobFormat.EccPublicBlob); + } + else + { + throw new InvalidOperationException(); + } + } + + internal static byte[] DeriveKey(ECDiffieHellman ecDiffieHellman, ECDiffieHellmanPublicKey publicKey) + { + if (ecDiffieHellman is ECDiffieHellmanCng cng) + { + return cng.DeriveKeyMaterial(publicKey); + } + else + { + throw new InvalidOperationException(); + } + } + + internal static RSA GetRSAFromCertificate(X509Certificate2 certificate) + { + RSAParameters parameters; + using (RSA rsaCsp = certificate.GetRSAPublicKey()) + { + parameters = rsaCsp.ExportParameters(includePrivateParameters: false); + } + RSACng rsaCng = new RSACng(); + rsaCng.ImportParameters(parameters); + return rsaCng; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs deleted file mode 100644 index 84a92c7ccc..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ /dev/null @@ -1,545 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.IdentityModel.Tokens.Jwt; -using System.Linq; -using System.Runtime.Caching; -using System.Security.Claims; -using System.Security.Cryptography; -using System.Text; -using System.Threading; -using Microsoft.IdentityModel.JsonWebTokens; -using Microsoft.IdentityModel.Logging; -using Microsoft.IdentityModel.Protocols; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Microsoft.IdentityModel.Tokens; - -// Azure Attestation Protocol Flow -// To start the attestation process, Sql Client sends the Protocol Id (i.e. 1), Nonce, Attestation Url and ECDH Public Key -// Sql Server uses attestation Url to attest the enclave and send the JWT to Sql client. -// Along with JWT, Sql server also sends enclave RSA public key, enclave Type, Enclave ECDH Public key. - -// To verify the chain of trust here is how it works -// JWT is signed by well-known signing keys which Sql client can download over https (via OpenIdConnect protocol). -// JWT contains the Enclave public key to safeguard against spoofing enclave RSA public key. -// Enclave ECDH public key signed by enclave RSA key - -// JWT validation -// To get the signing key for the JWT, we use OpenIdConnect API's. It download the signing keys from the well-known endpoint. -// We validate that JWT is signed, valid (i.e. not expired) and check the Issuer. - -// Claim validation: -// Validate the RSA public key send by Sql server matches the value specified in JWT. - -// Enclave Specific checks -// VSM -// Validate the nonce send by Sql client during start of attestation is same as that of specified in the JWT - -// SGX -// JWT for SGX enclave does not contain nonce claim. To workaround this limitation Sql Server sends the RSA public key XOR with the Nonce. -// In Sql server tempered with the nonce value then both Sql Server and client will not able to compute the same shared secret. - -namespace Microsoft.Data.SqlClient -{ - // Implementation of an Enclave provider (both for Sgx and Vsm) with Azure Attestation - internal class AzureAttestationEnclaveProvider : EnclaveProviderBase - { - #region Constants - private const int DiffieHellmanKeySize = 384; - private const int AzureBasedAttestationProtocolId = 1; - private const int SigningKeyRetryInSec = 3; - #endregion - - #region Members - // this is meta data endpoint for AAS provided by Windows team - // i.e. https:///.well-known/openid-configuration - // such as https://sql.azure.attest.com/.well-known/openid-configuration - private const string AttestationUrlSuffix = @"/.well-known/openid-configuration"; - - private static readonly MemoryCache OpenIdConnectConfigurationCache = new MemoryCache("OpenIdConnectConfigurationCache"); - #endregion - - #region Internal methods - // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. - // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) - { - GetEnclaveSessionHelper(enclaveSessionParameters, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); - } - - // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) - { - ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); - clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; - clientDHKey.HashAlgorithm = CngAlgorithm.Sha256; - byte[] attestationParam = PrepareAttestationParameters(attestationUrl, customData, customDataLength); - return new SqlEnclaveAttestationParameters(AzureBasedAttestationProtocolId, attestationParam, clientDHKey); - } - - // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - internal override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) - { - sqlEnclaveSession = null; - counter = 0; - try - { - ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); - sqlEnclaveSession = GetEnclaveSessionFromCache(enclaveSessionParameters, out counter); - if (sqlEnclaveSession == null) - { - if (!string.IsNullOrEmpty(enclaveSessionParameters.AttestationUrl) && customData != null && customDataLength > 0) - { - byte[] nonce = customData; - - IdentityModelEventSource.ShowPII = true; - - // Deserialize the payload - AzureAttestationInfo attestInfo = new AzureAttestationInfo(attestationInfo); - - // Validate the attestation info - VerifyAzureAttestationInfo(enclaveSessionParameters.AttestationUrl, attestInfo.EnclaveType, attestInfo.AttestationToken.AttestationToken, attestInfo.Identity, nonce); - - // Set up shared secret and validate signature - byte[] sharedSecret = GetSharedSecret(attestInfo.Identity, nonce, attestInfo.EnclaveType, attestInfo.EnclaveDHInfo, clientDHKey); - - // add session to cache - sqlEnclaveSession = AddEnclaveSessionToCache(enclaveSessionParameters, sharedSecret, attestInfo.SessionId, out counter); - } - else - { - throw new AlwaysEncryptedAttestationException(Strings.FailToCreateEnclaveSession); - } - } - } - finally - { - // As per current design, we want to minimize the number of create session calls. To achieve this we block all the GetEnclaveSession calls until the first call to - // GetEnclaveSession -> GetAttestationParameters -> CreateEnclaveSession completes or the event timeout happen. - // Case 1: When the first request successfully creates the session, then all outstanding GetEnclaveSession will use the current session. - // Case 2: When the first request unable to create the enclave session (may be due to some error or the first request doesn't require enclave computation) then in those case we set the event timeout to 0. - UpdateEnclaveSessionLockStatus(sqlEnclaveSession); - } - } - - // When overridden in a derived class, looks up and evicts an enclave session from the enclave session cache, if the provider implements session caching. - internal override void InvalidateEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, SqlEnclaveSession enclaveSessionToInvalidate) - { - InvalidateEnclaveSessionHelper(enclaveSessionParameters, enclaveSessionToInvalidate); - } - #endregion - - #region Internal Class - - // A model class representing the deserialization of the byte payload the client - // receives from SQL Server while setting up a session. - // Protocol format: - // 1. Total Size of the attestation blob as UINT - // 2. Size of Enclave RSA public key as UINT - // 3. Size of Attestation token as UINT - // 4. Enclave Type as UINT - // 5. Enclave RSA public key (raw key, of length #2) - // 6. Attestation token (of length #3) - // 7. Size of Session Id was UINT - // 8. Session id value - // 9. Size of enclave ECDH public key - // 10. Enclave ECDH public key (of length #9) - internal class AzureAttestationInfo - { - public uint TotalSize { get; set; } - - // The enclave's RSA Public Key. - // Needed to establish trust of the enclave. - // Used to verify the enclave's DiffieHellman info. - public EnclavePublicKey Identity { get; set; } - - // The enclave report from the SQL Server host's enclave. - public AzureAttestationToken AttestationToken { get; set; } - - // The id of the current session. - // Needed to set up a secure session between the client and enclave. - public long SessionId { get; set; } - - public EnclaveType EnclaveType { get; set; } - - // The DiffieHellman public key and signature of SQL Server host's enclave. - // Needed to set up a secure session between the client and enclave. - public EnclaveDiffieHellmanInfo EnclaveDHInfo { get; set; } - - public AzureAttestationInfo(byte[] attestationInfo) - { - try - { - int offset = 0; - - // Total size of the attestation info buffer - TotalSize = BitConverter.ToUInt32(attestationInfo, offset); - offset += sizeof(uint); - - // Size of the Enclave public key - int identitySize = BitConverter.ToInt32(attestationInfo, offset); - offset += sizeof(uint); - - // Size of the Azure attestation token - int attestationTokenSize = BitConverter.ToInt32(attestationInfo, offset); - offset += sizeof(uint); - - // Enclave type - int enclaveType = BitConverter.ToInt32(attestationInfo, offset); - EnclaveType = (EnclaveType)enclaveType; - offset += sizeof(uint); - - // Get the enclave public key - byte[] identityBuffer = attestationInfo.Skip(offset).Take(identitySize).ToArray(); - Identity = new EnclavePublicKey(identityBuffer); - offset += identitySize; - - // Get Azure attestation token - byte[] attestationTokenBuffer = attestationInfo.Skip(offset).Take(attestationTokenSize).ToArray(); - AttestationToken = new AzureAttestationToken(attestationTokenBuffer); - offset += attestationTokenSize; - - uint secureSessionInfoResponseSize = BitConverter.ToUInt32(attestationInfo, offset); - offset += sizeof(uint); - - SessionId = BitConverter.ToInt64(attestationInfo, offset); - offset += sizeof(long); - - int secureSessionBufferSize = Convert.ToInt32(secureSessionInfoResponseSize) - sizeof(uint); - byte[] secureSessionBuffer = attestationInfo.Skip(offset).Take(secureSessionBufferSize).ToArray(); - EnclaveDHInfo = new EnclaveDiffieHellmanInfo(secureSessionBuffer); - offset += Convert.ToInt32(EnclaveDHInfo.Size); - } - catch (Exception exception) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.FailToParseAttestationInfo, exception.Message)); - } - } - } - - // A managed model representing the output of EnclaveGetAttestationReport - // https://msdn.microsoft.com/en-us/library/windows/desktop/mt844233(v=vs.85).aspx - internal class AzureAttestationToken - { - public string AttestationToken { get; set; } - - public AzureAttestationToken(byte[] payload) - { - string jwt = System.Text.Encoding.Default.GetString(payload); - AttestationToken = jwt.Trim().Trim('"'); - } - } - #endregion Internal Class - - #region Private helpers - // Prepare the attestation data in following format - // Attestation Url length - // Attestation Url - // Size of nonce - // Nonce value - internal byte[] PrepareAttestationParameters(string attestationUrl, byte[] attestNonce, int attestNonceLength) - { - if (!string.IsNullOrEmpty(attestationUrl) && attestNonce != null && attestNonceLength > 0) - { - // In c# strings are not null terminated, so adding the null termination before serializing it - string attestationUrlLocal = attestationUrl + char.MinValue; - byte[] serializedAttestationUrl = Encoding.Unicode.GetBytes(attestationUrlLocal); - byte[] serializedAttestationUrlLength = BitConverter.GetBytes(serializedAttestationUrl.Length); - - // serializing nonce - byte[] serializedNonce = attestNonce; - byte[] serializedNonceLength = BitConverter.GetBytes(attestNonceLength); - - // Computing the total length of the data - int totalDataSize = serializedAttestationUrl.Length + serializedAttestationUrlLength.Length + serializedNonce.Length + serializedNonceLength.Length; - - int dataCopied = 0; - byte[] attestationParam = new byte[totalDataSize]; - - // copy the attestation url and url length - Buffer.BlockCopy(serializedAttestationUrlLength, 0, attestationParam, dataCopied, serializedAttestationUrlLength.Length); - dataCopied += serializedAttestationUrlLength.Length; - - Buffer.BlockCopy(serializedAttestationUrl, 0, attestationParam, dataCopied, serializedAttestationUrl.Length); - dataCopied += serializedAttestationUrl.Length; - - // copy the nonce and nonce length - Buffer.BlockCopy(serializedNonceLength, 0, attestationParam, dataCopied, serializedNonceLength.Length); - dataCopied += serializedNonceLength.Length; - - Buffer.BlockCopy(serializedNonce, 0, attestationParam, dataCopied, serializedNonce.Length); - dataCopied += serializedNonce.Length; - - return attestationParam; - } - else - { - throw new AlwaysEncryptedAttestationException(Strings.FailToCreateEnclaveSession); - } - } - - // Performs Attestation per the protocol used by Azure Attestation Service - private void VerifyAzureAttestationInfo(string attestationUrl, EnclaveType enclaveType, string attestationToken, EnclavePublicKey enclavePublicKey, byte[] nonce) - { - bool shouldForceUpdateSigningKeys = false; - string attestationInstanceUrl = GetAttestationInstanceUrl(attestationUrl); - - bool shouldRetryValidation; - bool isSignatureValid; - string exceptionMessage = string.Empty; - do - { - shouldRetryValidation = false; - - // Get the OpenId config object for the signing keys - OpenIdConnectConfiguration openIdConfig = GetOpenIdConfigForSigningKeys(attestationInstanceUrl, shouldForceUpdateSigningKeys); - - // Verify the token signature against the signing keys downloaded from meta data end point - bool isKeySigningExpired; - isSignatureValid = VerifyTokenSignature(attestationToken, attestationInstanceUrl, openIdConfig.SigningKeys, out isKeySigningExpired, out exceptionMessage); - - // In cases if we fail to validate the token, since we are using the old signing keys - // let's re-download the signing keys again and re-validate the token signature - if (!isSignatureValid && isKeySigningExpired && !shouldForceUpdateSigningKeys) - { - shouldForceUpdateSigningKeys = true; - shouldRetryValidation = true; - } - } - while (shouldRetryValidation); - - if (!isSignatureValid) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.AttestationTokenSignatureValidationFailed, exceptionMessage)); - } - - // Validate claims in the token - ValidateAttestationClaims(enclaveType, attestationToken, enclavePublicKey, nonce); - } - - // Returns the innermost exception value - private static string GetInnerMostExceptionMessage(Exception exception) - { - Exception exLocal = exception; - while (exLocal.InnerException != null) - { - exLocal = exLocal.InnerException; - } - - return exLocal.Message; - } - - // For the given attestation url it downloads the token signing keys from the well-known openid configuration end point. - // It also caches that information for 1 day to avoid DDOS attacks. - private OpenIdConnectConfiguration GetOpenIdConfigForSigningKeys(string url, bool forceUpdate) - { - OpenIdConnectConfiguration openIdConnectConfig = OpenIdConnectConfigurationCache[url] as OpenIdConnectConfiguration; - if (forceUpdate || openIdConnectConfig == null) - { - // Compute the meta data endpoint - string openIdMetadataEndpoint = url + AttestationUrlSuffix; - - try - { - IConfigurationManager configurationManager = new ConfigurationManager(openIdMetadataEndpoint, new OpenIdConnectConfigurationRetriever()); - openIdConnectConfig = configurationManager.GetConfigurationAsync(CancellationToken.None).Result; - } - catch (Exception exception) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.GetAttestationTokenSigningKeysFailed, GetInnerMostExceptionMessage(exception)), exception); - } - - OpenIdConnectConfigurationCache.Add(url, openIdConnectConfig, DateTime.UtcNow.AddDays(1)); - } - - return openIdConnectConfig; - } - - // Return the attestation instance url for given attestation url - // such as for https://sql.azure.attest.com/attest/SgxEnclave?api-version=2017-11-01 - // It will return https://sql.azure.attest.com - private string GetAttestationInstanceUrl(string attestationUrl) - { - Uri attestationUri = new Uri(attestationUrl); - return attestationUri.GetLeftPart(UriPartial.Authority); - } - - // Generate the list of valid issuer Url's (in case if tokenIssuerUrl is using default port) - private static ICollection GenerateListOfIssuers(string tokenIssuerUrl) - { - List issuerUrls = new List(); - - Uri tokenIssuerUri = new Uri(tokenIssuerUrl); - int port = tokenIssuerUri.Port; - bool isDefaultPort = tokenIssuerUri.IsDefaultPort; - - string issuerUrl = tokenIssuerUri.GetLeftPart(UriPartial.Authority); - issuerUrls.Add(issuerUrl); - - if (isDefaultPort) - { - issuerUrls.Add(String.Concat(issuerUrl, ":", port.ToString())); - } - - return issuerUrls; - } - - // Verifies the attestation token is signed by correct signing keys. - private bool VerifyTokenSignature(string attestationToken, string tokenIssuerUrl, ICollection issuerSigningKeys, out bool isKeySigningExpired, out string exceptionMessage) - { - exceptionMessage = string.Empty; - bool isSignatureValid = false; - isKeySigningExpired = false; - - // Configure the TokenValidationParameters - TokenValidationParameters validationParameters = - new TokenValidationParameters - { - RequireExpirationTime = true, - ValidateLifetime = true, - ValidateIssuer = true, - ValidateAudience = false, - RequireSignedTokens = true, - ValidIssuers = GenerateListOfIssuers(tokenIssuerUrl), - IssuerSigningKeys = issuerSigningKeys - }; - - try - { - SecurityToken validatedToken; - JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler(); - var token = handler.ValidateToken(attestationToken, validationParameters, out validatedToken); - isSignatureValid = true; - } - catch (SecurityTokenExpiredException securityException) - { - throw new AlwaysEncryptedAttestationException(Strings.ExpiredAttestationToken, securityException); - } - catch (SecurityTokenValidationException securityTokenException) - { - isKeySigningExpired = true; - - // Sleep for SigningKeyRetryInSec sec before retrying to download the signing keys again. - Thread.Sleep(SigningKeyRetryInSec * 1000); - exceptionMessage = GetInnerMostExceptionMessage(securityTokenException); - } - catch (Exception exception) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.InvalidAttestationToken, GetInnerMostExceptionMessage(exception))); - } - - return isSignatureValid; - } - - // Computes the SHA256 hash of the byte array - private byte[] ComputeSHA256(byte[] data) - { - byte[] result = null; - try - { - using (SHA256 sha256 = SHA256.Create()) - { - result = sha256.ComputeHash(data); - } - } - catch (Exception argumentException) - { - throw new AlwaysEncryptedAttestationException(Strings.InvalidArgumentToSHA256, argumentException); - } - return result; - } - - // Validate the claims in the attestation token - private void ValidateAttestationClaims(EnclaveType enclaveType, string attestationToken, EnclavePublicKey enclavePublicKey, byte[] nonce) - { - // Read the json token - JsonWebToken token = null; - try - { - JsonWebTokenHandler tokenHandler = new JsonWebTokenHandler(); - token = tokenHandler.ReadJsonWebToken(attestationToken); - } - catch (ArgumentException argumentException) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.FailToParseAttestationToken, argumentException.Message)); - } - - // Get all the claims from the token - Dictionary claims = new Dictionary(); - foreach (Claim claim in token.Claims.ToList()) - { - claims.Add(claim.Type, claim.Value); - } - - // Get Enclave held data claim and validate it with the Base64UrlEncode(enclave public key) - ValidateClaim(claims, "aas-ehd", enclavePublicKey.PublicKey); - - if (enclaveType == EnclaveType.Vbs) - { - // Get rp_data claim and validate it with the Base64UrlEncode(nonce) - ValidateClaim(claims, "rp_data", nonce); - } - } - - // Validate the claim value against the actual data - private void ValidateClaim(Dictionary claims, string claimName, byte[] actualData) - { - // Get required claim data - string claimData; - bool hasClaim = claims.TryGetValue(claimName, out claimData); - if (!hasClaim) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.MissingClaimInAttestationToken, claimName)); - } - - // Get the Base64Url of the actual data and compare it with claim - string encodedActualData = string.Empty; - try - { - encodedActualData = Base64UrlEncoder.Encode(actualData); - } - catch (Exception) - { - throw new AlwaysEncryptedAttestationException(Strings.InvalidArgumentToBase64UrlDecoder); - } - - bool hasValidClaim = String.Equals(encodedActualData, claimData, StringComparison.Ordinal); - if (!hasValidClaim) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.InvalidClaimInAttestationToken, claimName, claimData)); - } - } - - private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, byte[] nonce, EnclaveType enclaveType, EnclaveDiffieHellmanInfo enclaveDHInfo, ECDiffieHellmanCng clientDHKey) - { - byte[] enclaveRsaPublicKey = enclavePublicKey.PublicKey; - - // For SGX enclave we Sql server sends the enclave public key XOR'ed with Nonce. - // In case if Sql server replayed old JWT then shared secret will not match and hence client will not able to determine the updated enclave keys. - if (enclaveType == EnclaveType.Sgx) - { - for (int iterator = 0; iterator < enclaveRsaPublicKey.Length; iterator++) - { - enclaveRsaPublicKey[iterator] = (byte)(enclaveRsaPublicKey[iterator] ^ nonce[iterator % nonce.Length]); - } - } - - // Perform signature verification. The enclave's DiffieHellman public key was signed by the enclave's RSA public key. - CngKey cngkey = CngKey.Import(enclaveRsaPublicKey, CngKeyBlobFormat.GenericPublicBlob); - using (RSACng rsacng = new RSACng(cngkey)) - { - if (!rsacng.VerifyData(enclaveDHInfo.PublicKey, enclaveDHInfo.PublicKeySignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) - { - throw new ArgumentException(Strings.GetSharedSecretFailed); - } - } - - CngKey key = CngKey.Import(enclaveDHInfo.PublicKey, CngKeyBlobFormat.GenericPublicBlob); - return clientDHKey.DeriveKeyMaterial(key); - } - #endregion - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.CngCryto.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.CngCryto.cs deleted file mode 100644 index aceb598436..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/EnclaveDelegate.CngCryto.cs +++ /dev/null @@ -1,231 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Security.Cryptography; - -namespace Microsoft.Data.SqlClient -{ - internal sealed partial class EnclaveDelegate - { - private static readonly Dictionary s_enclaveProviders = new Dictionary(); - - /// - /// Create a new enclave session - /// - /// attestation protocol - /// enclave type - /// The set of parameters required for enclave session. - /// attestation info from SQL Server - /// attestation parameters - /// A set of extra data needed for attestating the enclave. - /// The length of the extra data needed for attestating the enclave. - internal void CreateEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, - byte[] attestationInfo, SqlEnclaveAttestationParameters attestationParameters, byte[] customData, int customDataLength) - { - lock (_lock) - { - SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - - sqlColumnEncryptionEnclaveProvider.GetEnclaveSession( - enclaveSessionParameters, - generateCustomData: false, - sqlEnclaveSession: out SqlEnclaveSession sqlEnclaveSession, - counter: out _, - customData: out _, - customDataLength: out _ - ); - - if (sqlEnclaveSession != null) - { - return; - } - - sqlColumnEncryptionEnclaveProvider.CreateEnclaveSession( - attestationInfo, - attestationParameters.ClientDiffieHellmanKey, - enclaveSessionParameters, - customData, - customDataLength, - out sqlEnclaveSession, - counter: out _ - ); - - if (sqlEnclaveSession == null) - { - throw SQL.NullEnclaveSessionReturnedFromProvider(enclaveType, enclaveSessionParameters.AttestationUrl); - } - } - } - - internal void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out byte[] customData, out int customDataLength) - { - GetEnclaveSession(attestationProtocol, enclaveType, enclaveSessionParameters, generateCustomData, out sqlEnclaveSession, out _, out customData, out customDataLength, throwIfNull: false); - } - - private void GetEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength, bool throwIfNull) - { - SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - sqlColumnEncryptionEnclaveProvider.GetEnclaveSession(enclaveSessionParameters, generateCustomData, out sqlEnclaveSession, out counter, out customData, out customDataLength); - - if (throwIfNull && sqlEnclaveSession == null) - { - throw SQL.NullEnclaveSessionDuringQueryExecution(enclaveType, enclaveSessionParameters.AttestationUrl); - } - } - - internal void InvalidateEnclaveSession(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, SqlEnclaveSession enclaveSession) - { - SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - sqlColumnEncryptionEnclaveProvider.InvalidateEnclaveSession(enclaveSessionParameters, enclaveSession); - } - - - private SqlColumnEncryptionEnclaveProvider GetEnclaveProvider(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType) - { - if (!s_enclaveProviders.TryGetValue(attestationProtocol, out SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider)) - { - switch (attestationProtocol) - { - case SqlConnectionAttestationProtocol.AAS: - AzureAttestationEnclaveProvider azureAttestationEnclaveProvider = new AzureAttestationEnclaveProvider(); - s_enclaveProviders[attestationProtocol] = azureAttestationEnclaveProvider; - sqlColumnEncryptionEnclaveProvider = s_enclaveProviders[attestationProtocol]; - break; - - case SqlConnectionAttestationProtocol.HGS: - HostGuardianServiceEnclaveProvider hostGuardianServiceEnclaveProvider = new HostGuardianServiceEnclaveProvider(); - s_enclaveProviders[attestationProtocol] = hostGuardianServiceEnclaveProvider; - sqlColumnEncryptionEnclaveProvider = s_enclaveProviders[attestationProtocol]; - break; - -#if ENCLAVE_SIMULATOR - case SqlConnectionAttestationProtocol.SIM: - SimulatorEnclaveProvider simulatorEnclaveProvider = new SimulatorEnclaveProvider(); - s_enclaveProviders[attestationProtocol] = (SqlColumnEncryptionEnclaveProvider)simulatorEnclaveProvider; - sqlColumnEncryptionEnclaveProvider = s_enclaveProviders[attestationProtocol]; - break; -#endif - - default: - break; - } - } - - if (sqlColumnEncryptionEnclaveProvider == null) - { - throw SQL.EnclaveProviderNotFound(enclaveType, ConvertAttestationProtocolToString(attestationProtocol)); - } - - return sqlColumnEncryptionEnclaveProvider; - } - - /// - /// Generate the byte package that needs to be sent to the enclave - /// - /// attestation protocol - /// Keys to be sent to enclave - /// - /// enclave type - /// The set of parameters required for enclave session. - /// connection executing the query - /// - internal EnclavePackage GenerateEnclavePackage(SqlConnectionAttestationProtocol attestationProtocol, Dictionary keysToBeSentToEnclave, string queryText, string enclaveType, EnclaveSessionParameters enclaveSessionParameters, SqlConnection connection) - { - SqlEnclaveSession sqlEnclaveSession; - long counter; - - try - { - GetEnclaveSession( - attestationProtocol, - enclaveType, - enclaveSessionParameters, - generateCustomData: false, - sqlEnclaveSession: out sqlEnclaveSession, - counter: out counter, - customData: out _, - customDataLength: out _, - throwIfNull: true - ); - } - catch (Exception e) - { - throw new RetryableEnclaveQueryExecutionException(e.Message, e); - } - - List decryptedKeysToBeSentToEnclave = GetDecryptedKeysToBeSentToEnclave(keysToBeSentToEnclave, enclaveSessionParameters.ServerName, connection); - byte[] queryStringHashBytes = ComputeQueryStringHash(queryText); - byte[] keyBytePackage = GenerateBytePackageForKeys(counter, queryStringHashBytes, decryptedKeysToBeSentToEnclave); - byte[] sessionKey = sqlEnclaveSession.GetSessionKey(); - byte[] encryptedBytePackage = EncryptBytePackage(keyBytePackage, sessionKey, enclaveSessionParameters.ServerName); - byte[] enclaveSessionHandle = BitConverter.GetBytes(sqlEnclaveSession.SessionId); - byte[] byteArrayToBeSentToEnclave = CombineByteArrays(enclaveSessionHandle, encryptedBytePackage); - return new EnclavePackage(byteArrayToBeSentToEnclave, sqlEnclaveSession); - } - - - internal SqlEnclaveAttestationParameters GetAttestationParameters(SqlConnectionAttestationProtocol attestationProtocol, string enclaveType, string attestationUrl, byte[] customData, int customDataLength) - { - SqlColumnEncryptionEnclaveProvider sqlColumnEncryptionEnclaveProvider = GetEnclaveProvider(attestationProtocol, enclaveType); - return sqlColumnEncryptionEnclaveProvider.GetAttestationParameters(attestationUrl, customData, customDataLength); - } - - internal byte[] GetSerializedAttestationParameters(SqlEnclaveAttestationParameters sqlEnclaveAttestationParameters, string enclaveType) - { - byte[] attestationProtocolBytes = null; - byte[] attestationProtocolInputLengthBytes = null; - byte[] clientDHPublicKeyLengthBytes = null; - int attestationProtocolInt = sqlEnclaveAttestationParameters.Protocol; - - attestationProtocolBytes = GetUintBytes(enclaveType, attestationProtocolInt, "attestationProtocol"); - - if (attestationProtocolBytes == null) - { - throw SQL.NullArgumentInternal(nameof(attestationProtocolBytes), nameof(EnclaveDelegate), nameof(GetSerializedAttestationParameters)); - } - - byte[] attestationProtocolInputBytes = sqlEnclaveAttestationParameters.GetInput(); - - attestationProtocolInputLengthBytes = GetUintBytes(enclaveType, attestationProtocolInputBytes.Length, "attestationProtocolInputLength"); - - if (attestationProtocolInputLengthBytes == null) - { - throw SQL.NullArgumentInternal(nameof(attestationProtocolInputLengthBytes), nameof(EnclaveDelegate), nameof(GetSerializedAttestationParameters)); - } - - byte[] clientDHPublicKey = sqlEnclaveAttestationParameters.ClientDiffieHellmanKey.Key.Export(CngKeyBlobFormat.EccPublicBlob); - - clientDHPublicKeyLengthBytes = GetUintBytes(enclaveType, clientDHPublicKey.Length, "clientDHPublicKeyLength"); - - if (clientDHPublicKeyLengthBytes == null) - { - throw SQL.NullArgumentInternal(nameof(clientDHPublicKeyLengthBytes), nameof(EnclaveDelegate), nameof(GetSerializedAttestationParameters)); - } - - return CombineByteArrays(attestationProtocolBytes, attestationProtocolInputLengthBytes, attestationProtocolInputBytes, clientDHPublicKeyLengthBytes, clientDHPublicKey); - } - - private string ConvertAttestationProtocolToString(SqlConnectionAttestationProtocol attestationProtocol) - { - switch (attestationProtocol) - { - case SqlConnectionAttestationProtocol.AAS: - return "AAS"; - - case SqlConnectionAttestationProtocol.HGS: - return "HGS"; - -#if ENCLAVE_SIMULATOR - case SqlConnectionAttestationProtocol.SIM: - return "SIM"; -#endif - - default: - return "NotSpecified"; - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs index 00f05363bc..9017b84717 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlColumnEncryptionEnclaveProvider.cs @@ -17,7 +17,7 @@ internal abstract class SqlColumnEncryptionEnclaveProvider internal abstract SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength); /// - internal abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellmanCng clientDiffieHellmanKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); + internal abstract void CreateEnclaveSession(byte[] enclaveAttestationInfo, ECDiffieHellman clientDiffieHellmanKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter); /// internal abstract void InvalidateEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, SqlEnclaveSession enclaveSession); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index aa054ae5a1..e962a5695c 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -31,7 +31,8 @@ namespace Microsoft.Data.SqlClient [ DefaultEvent("RecordsAffected"), ToolboxItem(true), - Designer("Microsoft.VSDesigner.Data.VS.SqlCommandDesigner, " + AssemblyRef.MicrosoftVSDesigner) + Designer("Microsoft.VSDesigner.Data.VS.SqlCommandDesigner, " + AssemblyRef.MicrosoftVSDesigner), + DesignerCategory("") ] public sealed class SqlCommand : DbCommand, ICloneable { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs index 0ee215f1fd..11839689f5 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs @@ -14,6 +14,7 @@ namespace Microsoft.Data.SqlClient { /// + [DesignerCategory("")] public sealed class SqlCommandBuilder : DbCommandBuilder { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs index bcd587ed0f..bd5cfaad8f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -35,7 +35,10 @@ namespace Microsoft.Data.SqlClient using Microsoft.Data.Common; /// - [DefaultEvent("InfoMessage")] + [ + DefaultEvent("InfoMessage"), + DesignerCategory("") + ] public sealed partial class SqlConnection : DbConnection, ICloneable { @@ -64,6 +67,9 @@ static private readonly Dictionary /// private static IReadOnlyDictionary s_globalCustomColumnEncryptionKeyStoreProviders; + /// Instance-level list of custom key store providers. It can be set more than once by the user. + private IReadOnlyDictionary _customColumnEncryptionKeyStoreProviders; + // Lock to control setting of s_globalCustomColumnEncryptionKeyStoreProviders private static readonly object s_globalCustomColumnEncryptionKeyProvidersLock = new object(); @@ -161,6 +167,23 @@ static public void RegisterColumnEncryptionKeyStoreProviders(IDictionary + public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(IDictionary customProviders) + { + ValidateCustomProviders(customProviders); + + // Create a temporary dictionary and then add items from the provided dictionary. + // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs + // in the provided customerProviders dictionary. + Dictionary customColumnEncryptionKeyStoreProviders = + new Dictionary(customProviders, StringComparer.OrdinalIgnoreCase); + + // Set the dictionary to the ReadOnly dictionary. + // This method can be called more than once. Re-registering a new collection will replace the + // old collection of providers. + _customColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders; + } + private static void ValidateCustomProviders(IDictionary customProviders) { // Throw when the provided dictionary is null. @@ -213,6 +236,13 @@ static internal bool TryGetColumnEncryptionKeyStoreProvider(string providerName, return true; } + // instance-level custom provider cache takes precedence over global cache + if (connection._customColumnEncryptionKeyStoreProviders != null && + connection._customColumnEncryptionKeyStoreProviders.Count > 0) + { + return connection._customColumnEncryptionKeyStoreProviders.TryGetValue(providerName, out columnKeyStoreProvider); + } + lock (s_globalCustomColumnEncryptionKeyProvidersLock) { // If custom provider is not set, then return false @@ -243,6 +273,11 @@ internal static List GetColumnEncryptionSystemKeyStoreProviders() /// Combined list of provider names internal static List GetColumnEncryptionCustomKeyStoreProviders(SqlConnection connection) { + if (connection._customColumnEncryptionKeyStoreProviders != null && + connection._customColumnEncryptionKeyStoreProviders.Count > 0) + { + return connection._customColumnEncryptionKeyStoreProviders.Keys.ToList(); + } if (s_globalCustomColumnEncryptionKeyStoreProviders != null) { return s_globalCustomColumnEncryptionKeyStoreProviders.Keys.ToList(); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCredential.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCredential.cs deleted file mode 100644 index 93ae8f12ed..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCredential.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Security; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - /// - // Represent a pair of user id and password which to be used for SQL Authentication - // SqlCredential takes password as SecureString which is better way to store security sensitive information - // This class is immutable - public sealed class SqlCredential - { - string _userId; - SecureString _password; - - /// - // PUBLIC CONSTRUCTOR - // SqlCredential - // userId: userId - // password: password - public SqlCredential(string userId, SecureString password) - { - if (userId == null) - { - throw ADP.ArgumentNull("userId"); - } - - if (userId.Length > TdsEnums.MAXLEN_CLIENTID) - { - throw ADP.InvalidArgumentLength("userId", TdsEnums.MAXLEN_CLIENTID); - } - - if (password == null) - { - throw ADP.ArgumentNull("password"); - } - - if (password.Length > TdsEnums.MAXLEN_CLIENTSECRET) - { - throw ADP.InvalidArgumentLength("password", TdsEnums.MAXLEN_CLIENTSECRET); - } - - if (!password.IsReadOnly()) - { - throw ADP.MustBeReadOnly("password"); - } - - _userId = userId; - _password = password; - } - - /// - // PUBLIC PROPERTIES - public string UserId - { - get - { - return _userId; - } - } - - /// - public SecureString Password - { - get - { - return _password; - } - } - } -} // Microsoft.Data.SqlClient namespace - - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs index 68da2c6f4e..0e223df8ba 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataAdapter.cs @@ -15,7 +15,8 @@ namespace Microsoft.Data.SqlClient [ DefaultEvent("RowUpdated"), ToolboxItem("Microsoft.VSDesigner.Data.VS.SqlDataAdapterToolboxItem, " + AssemblyRef.MicrosoftVSDesigner), - Designer("Microsoft.VSDesigner.Data.VS.SqlDataAdapterDesigner, " + AssemblyRef.MicrosoftVSDesigner) + Designer("Microsoft.VSDesigner.Data.VS.SqlDataAdapterDesigner, " + AssemblyRef.MicrosoftVSDesigner), + DesignerCategory("") ] public sealed class SqlDataAdapter : DbDataAdapter, IDbDataAdapter, ICloneable { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs index 082de06d3f..7bf191e837 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs @@ -391,24 +391,21 @@ public void SinglePhaseCommit(SysTx.SinglePhaseEnlistment enlistment) #else { #endif //DEBUG - // If the connection is doomed, we can be certain that the - // transaction will eventually be rolled back, and we shouldn't - // attempt to commit it. - if (connection.IsConnectionDoomed) + lock (connection) { - lock (connection) + // If the connection is doomed, we can be certain that the + // transaction will eventually be rolled back or has already been aborted externally, and we shouldn't + // attempt to commit it. + if (connection.IsConnectionDoomed) { _active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done. _connection = null; - } - enlistment.Aborted(SQL.ConnectionDoomed()); - } - else - { - Exception commitException; - lock (connection) + enlistment.Aborted(SQL.ConnectionDoomed()); + } + else { + Exception commitException; try { // Now that we've acquired the lock, make sure we still have valid state for this operation. @@ -437,40 +434,40 @@ public void SinglePhaseCommit(SysTx.SinglePhaseEnlistment enlistment) ADP.TraceExceptionWithoutRethrow(e); connection.DoomThisConnection(); } - } - if (commitException != null) - { - // connection.ExecuteTransaction failed with exception - if (_internalTransaction.IsCommitted) - { - // Even though we got an exception, the transaction - // was committed by the server. - enlistment.Committed(); - } - else if (_internalTransaction.IsAborted) + if (commitException != null) { - // The transaction was aborted, report that to - // SysTx. - enlistment.Aborted(commitException); + // connection.ExecuteTransaction failed with exception + if (_internalTransaction.IsCommitted) + { + // Even though we got an exception, the transaction + // was committed by the server. + enlistment.Committed(); + } + else if (_internalTransaction.IsAborted) + { + // The transaction was aborted, report that to + // SysTx. + enlistment.Aborted(commitException); + } + else + { + // The transaction is still active, we cannot + // know the state of the transaction. + enlistment.InDoubt(commitException); + } + + // We eat the exception. This is called on the SysTx + // thread, not the applications thread. If we don't + // eat the exception an UnhandledException will occur, + // causing the process to FailFast. } - else + + connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); + if (commitException == null) { - // The transaction is still active, we cannot - // know the state of the transaction. - enlistment.InDoubt(commitException); + // connection.ExecuteTransaction succeeded + enlistment.Committed(); } - - // We eat the exception. This is called on the SysTx - // thread, not the applications thread. If we don't - // eat the exception an UnhandledException will occur, - // causing the process to FailFast. - } - - connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); - if (commitException == null) - { - // connection.ExecuteTransaction succeeded - enlistment.Committed(); } } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs deleted file mode 100644 index 3422180d8e..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Security.Cryptography; - -namespace Microsoft.Data.SqlClient -{ - - /// - internal class SqlEnclaveAttestationParameters - { - - private static readonly string _clientDiffieHellmanKeyName = "ClientDiffieHellmanKey"; - private static readonly string _inputName = "input"; - private static readonly string _className = "EnclaveAttestationParameters"; - - private readonly byte[] _input; - - /// - internal int Protocol { get; } - - - /// - internal ECDiffieHellmanCng ClientDiffieHellmanKey { get; } - - /// - internal byte[] GetInput() - { - return Clone(_input); - } - - /// - /// Deep copy the array into a new array - /// - /// - /// - private byte[] Clone(byte[] arrayToClone) - { - - if (null == arrayToClone) - { - return null; - } - - byte[] returnValue = new byte[arrayToClone.Length]; - - for (int i = 0; i < arrayToClone.Length; i++) - { - returnValue[i] = arrayToClone[i]; - } - - return returnValue; - } - - /// - internal SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellmanCng clientDiffieHellmanKey) - { - if (null == clientDiffieHellmanKey) - { throw SQL.NullArgumentInConstructorInternal(_clientDiffieHellmanKeyName, _className); } - if (null == input) - { throw SQL.NullArgumentInConstructorInternal(_inputName, _className); } - - _input = input; - Protocol = protocol; - ClientDiffieHellmanKey = clientDiffieHellmanKey; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs deleted file mode 100644 index 11d24a0a27..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Reflection; -using Microsoft.Data.SqlClient.Server; - -namespace Microsoft.Data.SqlClient -{ - internal class SqlUdtInfo - { - internal readonly Format SerializationFormat; - internal readonly bool IsByteOrdered; - internal readonly bool IsFixedLength; - internal readonly int MaxByteSize; - internal readonly string Name; - internal readonly string ValidationMethodName; - - private SqlUdtInfo(SqlUserDefinedTypeAttribute attr) - { - SerializationFormat = (Format)attr.Format; - IsByteOrdered = attr.IsByteOrdered; - IsFixedLength = attr.IsFixedLength; - MaxByteSize = attr.MaxByteSize; - Name = attr.Name; - ValidationMethodName = attr.ValidationMethodName; - } - internal static SqlUdtInfo GetFromType(Type target) - { - SqlUdtInfo udtAttr = TryGetFromType(target); - if (udtAttr == null) - { - Type myType = typeof(InvalidUdtException); - var arguments = new Type[] { typeof(Type), typeof(String) }; - MethodInfo Create = myType.GetMethod("Create", arguments); - Create.Invoke(null, new object[] { Strings.SqlUdtReason_NoUdtAttribute }); - } - return udtAttr; - } - - // VSTFDEVDIV 479671: Type.GetCustomAttributes is an time-expensive call. - // Improve UDT serialization performance by caching the resulted UDT type information using type-safe dictionary. - // Use a per-thread cache, so we do not need to synchronize access to it - [ThreadStatic] - private static Dictionary m_types2UdtInfo; - - internal static SqlUdtInfo TryGetFromType(Type target) - { - if (m_types2UdtInfo == null) - m_types2UdtInfo = new Dictionary(); - - SqlUdtInfo udtAttr = null; - if (!m_types2UdtInfo.TryGetValue(target, out udtAttr)) - { - // query SqlUserDefinedTypeAttribute first time and cache the result - object[] attr = target.GetCustomAttributes(typeof(SqlUserDefinedTypeAttribute), false); - if (attr != null && attr.Length == 1) - { - udtAttr = new SqlUdtInfo((SqlUserDefinedTypeAttribute)attr[0]); - } - m_types2UdtInfo.Add(target, udtAttr); - } - return udtAttr; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs deleted file mode 100644 index 6f7c62ce66..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ /dev/null @@ -1,367 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Linq; -using System.Runtime.Caching; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Threading; - -namespace Microsoft.Data.SqlClient -{ - internal abstract class VirtualizationBasedSecurityEnclaveProviderBase : EnclaveProviderBase - { - #region Members - - private static readonly MemoryCache rootSigningCertificateCache = new MemoryCache("RootSigningCertificateCache"); - - #endregion - - #region Constants - - private const int DiffieHellmanKeySize = 384; - private const int VsmHGSProtocolId = 3; - - // ENCLAVE_IDENTITY related constants - private static readonly EnclaveIdentity ExpectedPolicy = new EnclaveIdentity() - { - OwnerId = new byte[] - { - 0x10, 0x20, 0x30, 0x40, 0x41, 0x31, 0x21, 0x11, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }, - - UniqueId = new byte[] { }, - - // This field is calculated as follows: - // "Fixed Microsoft GUID" = {845319A6-706C-47BC-A7E8-5137B0BC750D} - // CN of the certificate that signed the file - // Opus Info - Authenticated Attribute that contains a Name and a URL - // - // In our case the Opus Info is: - // Description: Microsoft SQL Server Always Encrypted VBS Enclave Library, - // Description URL: https://go.microsoft.com/fwlink/?linkid=2018716 - AuthorId = new byte[] - { - 0x04, 0x37, 0xCA, 0xE2, 0x53, 0x7D, 0x8B, 0x9B, - 0x07, 0x76, 0xB6, 0x1B, 0x11, 0xE6, 0xCE, 0xD3, - 0xD2, 0x32, 0xE9, 0x30, 0x8F, 0x60, 0xE2, 0x1A, - 0xDA, 0xB2, 0xFD, 0x91, 0xE3, 0xDA, 0x95, 0x98 - }, - - FamilyId = new byte[] - { - 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }, - - ImageId = new byte[] - { - // This value should be same as defined in sqlserver code Sql/Ntdbms/aetm/enclave/dllmain.cpp - 0x19, 0x17, 0x12, 0x00, 0x01, 0x05, 0x20, 0x13, - 0x00, 0x05, 0x14, 0x03, 0x12, 0x01, 0x22, 0x05 - }, - - EnclaveSvn = 0, - - SecureKernelSvn = 0, - - PlatformSvn = 1, - - // 0: ENCLAVE_VBS_FLAG_NO_DEBUG in ds_main; Flag does not permit debug enclaves - Flags = 0, - - SigningLevel = 0, - - Reserved = 0 - }; - - #endregion - - #region Internal methods - - // When overridden in a derived class, looks up an existing enclave session information in the enclave session cache. - // If the enclave provider doesn't implement enclave session caching, this method is expected to return null in the sqlEnclaveSession parameter. - internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, bool generateCustomData, out SqlEnclaveSession sqlEnclaveSession, out long counter, out byte[] customData, out int customDataLength) - { - GetEnclaveSessionHelper(enclaveSessionParameters, false, out sqlEnclaveSession, out counter, out customData, out customDataLength); - } - - // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. - internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) - { - ECDiffieHellmanCng clientDHKey = new ECDiffieHellmanCng(DiffieHellmanKeySize); - clientDHKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash; - clientDHKey.HashAlgorithm = CngAlgorithm.Sha256; - return new SqlEnclaveAttestationParameters(VsmHGSProtocolId, new byte[] { }, clientDHKey); - } - - // When overridden in a derived class, performs enclave attestation, generates a symmetric key for the session, creates a an enclave session and stores the session information in the cache. - internal override void CreateEnclaveSession(byte[] attestationInfo, ECDiffieHellmanCng clientDHKey, EnclaveSessionParameters enclaveSessionParameters, byte[] customData, int customDataLength, out SqlEnclaveSession sqlEnclaveSession, out long counter) - { - sqlEnclaveSession = null; - counter = 0; - try - { - ThreadRetryCache.Remove(Thread.CurrentThread.ManagedThreadId.ToString()); - sqlEnclaveSession = GetEnclaveSessionFromCache(enclaveSessionParameters, out counter); - if (sqlEnclaveSession == null) - { - if (!string.IsNullOrEmpty(enclaveSessionParameters.AttestationUrl)) - { - // Deserialize the payload - AttestationInfo info = new AttestationInfo(attestationInfo); - - // Verify enclave policy matches expected policy - VerifyEnclavePolicy(info.EnclaveReportPackage); - - // Perform Attestation per VSM protocol - VerifyAttestationInfo(enclaveSessionParameters.AttestationUrl, info.HealthReport, info.EnclaveReportPackage); - - // Set up shared secret and validate signature - byte[] sharedSecret = GetSharedSecret(info.Identity, info.EnclaveDHInfo, clientDHKey); - - // add session to cache - sqlEnclaveSession = AddEnclaveSessionToCache(enclaveSessionParameters, sharedSecret, info.SessionId, out counter); - } - else - { - throw new AlwaysEncryptedAttestationException(Strings.FailToCreateEnclaveSession); - } - } - } - finally - { - UpdateEnclaveSessionLockStatus(sqlEnclaveSession); - } - } - - // When overridden in a derived class, looks up and evicts an enclave session from the enclave session cache, if the provider implements session caching. - internal override void InvalidateEnclaveSession(EnclaveSessionParameters enclaveSessionParameters, SqlEnclaveSession enclaveSessionToInvalidate) - { - InvalidateEnclaveSessionHelper(enclaveSessionParameters, enclaveSessionToInvalidate); - } - - #endregion - - #region Private helpers - - // Performs Attestation per the protocol used by Virtual Secure Modules. - private void VerifyAttestationInfo(string attestationUrl, HealthReport healthReport, EnclaveReportPackage enclaveReportPackage) - { - bool shouldRetryValidation; - bool shouldForceUpdateSigningKeys = false; - do - { - shouldRetryValidation = false; - - // Get HGS Root signing certs from HGS - X509Certificate2Collection signingCerts = GetSigningCertificate(attestationUrl, shouldForceUpdateSigningKeys); - - // Verify SQL Health report root chain of trust is the HGS root signing cert - X509ChainStatusFlags chainStatus = VerifyHealthReportAgainstRootCertificate(signingCerts, healthReport.Certificate); - if (chainStatus != X509ChainStatusFlags.NoError) - { - // In cases if we fail to validate the health report, it might be possible that we are using old signing keys - // let's re-download the signing keys again and re-validate the health report - if (!shouldForceUpdateSigningKeys) - { - shouldForceUpdateSigningKeys = true; - shouldRetryValidation = true; - } - else - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.VerifyHealthCertificateChainFormat, attestationUrl, chainStatus)); - } - } - } while (shouldRetryValidation); - - // Verify enclave report is signed by IDK_S from health report - VerifyEnclaveReportSignature(enclaveReportPackage, healthReport.Certificate); - } - - // Makes a web request to the provided url and returns the response as a byte[] - protected abstract byte[] MakeRequest(string url); - - // Gets the root signing certificate for the provided attestation service. - // If the certificate does not exist in the cache, this will make a call to the - // attestation service's "/signingCertificates" endpoint. This endpoint can - // return multiple certificates if the attestation service consists - // of multiple nodes. - private X509Certificate2Collection GetSigningCertificate(string attestationUrl, bool forceUpdate) - { - attestationUrl = GetAttestationUrl(attestationUrl); - X509Certificate2Collection signingCertificates = (X509Certificate2Collection)rootSigningCertificateCache[attestationUrl]; - if (forceUpdate || signingCertificates == null || AnyCertificatesExpired(signingCertificates)) - { - byte[] data = MakeRequest(attestationUrl); - var certificateCollection = new X509Certificate2Collection(); - - try - { - certificateCollection.Import(data); - } - catch (CryptographicException exception) - { - throw new AlwaysEncryptedAttestationException(String.Format(Strings.GetAttestationSigningCertificateFailedInvalidCertificate, attestationUrl), exception); - } - - rootSigningCertificateCache.Add(attestationUrl, certificateCollection, DateTime.Now.AddDays(1)); - } - - return (X509Certificate2Collection)rootSigningCertificateCache[attestationUrl]; - } - - // Return the endpoint for given attestation url - protected abstract string GetAttestationUrl(string attestationUrl); - - // Checks if any certificates in the collection are expired - private bool AnyCertificatesExpired(X509Certificate2Collection certificates) - { - return certificates.OfType().Any(c => c.NotAfter < DateTime.Now); - } - - // Verifies that a chain of trust can be built from the health report provided - // by SQL Server and the attestation service's root signing certificate(s). - private X509ChainStatusFlags VerifyHealthReportAgainstRootCertificate(X509Certificate2Collection signingCerts, X509Certificate2 healthReportCert) - { - var chain = new X509Chain(); - - foreach (var cert in signingCerts) - { - chain.ChainPolicy.ExtraStore.Add(cert); - } - - chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - - if (!chain.Build(healthReportCert)) - { - bool untrustedRoot = false; - - // iterate over the chain status to check why the build failed - foreach (X509ChainStatus status in chain.ChainStatus) - { - if (status.Status == X509ChainStatusFlags.UntrustedRoot) - { - untrustedRoot = true; - } - else - { - return status.Status; - } - } - - // if the chain failed with untrusted root, this could be because the client doesn't have the root cert - // installed. If the chain's untrusted root cert has the same thumbprint as the signing cert, then we - // do trust it. - if (untrustedRoot) - { - // iterate through the certificate chain, starting at the root since it's likely the - // signing certificate is the root - for (int i = 0; i < chain.ChainElements.Count; i++) - { - X509ChainElement element = chain.ChainElements[chain.ChainElements.Count - 1 - i]; - - foreach (X509Certificate2 cert in signingCerts) - { - if (element.Certificate.Thumbprint == cert.Thumbprint) - { - return X509ChainStatusFlags.NoError; - } - } - } - - // in the case where we didn't find matching thumbprint - return X509ChainStatusFlags.UntrustedRoot; - } - } - - return X509ChainStatusFlags.NoError; - } - - // Verifies the enclave report signature using the health report. - private void VerifyEnclaveReportSignature(EnclaveReportPackage enclaveReportPackage, X509Certificate2 healthReportCert) - { - // Check if report is formatted correctly - UInt32 calculatedSize = Convert.ToUInt32(enclaveReportPackage.PackageHeader.GetSizeInPayload()) + enclaveReportPackage.PackageHeader.SignedStatementSize + enclaveReportPackage.PackageHeader.SignatureSize; - - if (calculatedSize != enclaveReportPackage.PackageHeader.PackageSize) - { - throw new ArgumentException(Strings.VerifyEnclaveReportFormatFailed); - } - - // IDK_S is contained in healthReport cert public key - RSA rsacsp = healthReportCert.GetRSAPublicKey(); - RSAParameters rsaparams = rsacsp.ExportParameters(includePrivateParameters: false); - RSACng rsacng = new RSACng(); - rsacng.ImportParameters(rsaparams); - - if (!rsacng.VerifyData(enclaveReportPackage.ReportAsBytes, enclaveReportPackage.SignatureBlob, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)) - { - throw new ArgumentException(Strings.VerifyEnclaveReportFailed); - } - } - - // Verifies the enclave policy matches expected policy. - private void VerifyEnclavePolicy(EnclaveReportPackage enclaveReportPackage) - { - EnclaveIdentity identity = enclaveReportPackage.Report.Identity; - - VerifyEnclavePolicyProperty("OwnerId", identity.OwnerId, ExpectedPolicy.OwnerId); - VerifyEnclavePolicyProperty("AuthorId", identity.AuthorId, ExpectedPolicy.AuthorId); - VerifyEnclavePolicyProperty("FamilyId", identity.FamilyId, ExpectedPolicy.FamilyId); - VerifyEnclavePolicyProperty("ImageId", identity.ImageId, ExpectedPolicy.ImageId); - VerifyEnclavePolicyProperty("EnclaveSvn", identity.EnclaveSvn, ExpectedPolicy.EnclaveSvn); - VerifyEnclavePolicyProperty("SecureKernelSvn", identity.SecureKernelSvn, ExpectedPolicy.SecureKernelSvn); - VerifyEnclavePolicyProperty("PlatformSvn", identity.PlatformSvn, ExpectedPolicy.PlatformSvn); - - // This is a check that the enclave is running without debug support or not. - // - if (identity.Flags != ExpectedPolicy.Flags) - { - throw new InvalidOperationException(Strings.VerifyEnclaveDebuggable); - } - } - - // Verifies a byte[] enclave policy property - private void VerifyEnclavePolicyProperty(string property, byte[] actual, byte[] expected) - { - if (!actual.SequenceEqual(expected)) - { - string exceptionMessage = String.Format(Strings.VerifyEnclavePolicyFailedFormat, property, BitConverter.ToString(actual), BitConverter.ToString(expected)); - throw new ArgumentException(exceptionMessage); - } - } - - // Verifies a uint enclave policy property - private void VerifyEnclavePolicyProperty(string property, uint actual, uint expected) - { - if (actual < expected) - { - string exceptionMessage = String.Format(Strings.VerifyEnclavePolicyFailedFormat, property, actual, expected); - throw new ArgumentException(exceptionMessage); - } - } - - // Derives the shared secret between the client and enclave. - private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, EnclaveDiffieHellmanInfo enclaveDHInfo, ECDiffieHellmanCng clientDHKey) - { - // Perform signature verification. The enclave's DiffieHellman public key was signed by the enclave's RSA public key. - CngKey cngkey = CngKey.Import(enclavePublicKey.PublicKey, CngKeyBlobFormat.GenericPublicBlob); - RSACng rsacng = new RSACng(cngkey); - if (!rsacng.VerifyData(enclaveDHInfo.PublicKey, enclaveDHInfo.PublicKeySignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) - { - throw new ArgumentException(Strings.GetSharedSecretFailed); - } - - CngKey key = CngKey.Import(enclaveDHInfo.PublicKey, CngKeyBlobFormat.GenericPublicBlob); - return clientDHKey.DeriveKeyMaterial(key); - } - - #endregion - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx index da5159755b..6b5765a690 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.de.resx @@ -4584,24 +4584,6 @@ "Authentication={0}" kann nicht verwendet werden, wenn die Eigenschaft "Credential" festgelegt wurde. - - Das Zugriffstoken konnte nicht abgerufen werden. - - - Mit dem Endpunkt der verwalteten Identität kann keine Verbindung hergestellt werden. Stellen Sie sicher, dass Sie eine Azure-Ressource mit eingerichteter Identität ausführen. - - - Es wurde versucht, ein Token mithilfe einer verwalteten Identität abzurufen. - - - Mit dem Instance Metadata Service (IMDS) kann keine Verbindung hergestellt werden. Die Anforderung an den Tokenendpunkt der verwalteten Identität wird übersprungen. - - - Es wurde ein nicht wiederholbarer Fehler empfangen. - - - Fehler nach 5 Wiederholungen. - Unerwarteter Typ beim Deserialisieren erkannt. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx index fc93a8eb3e..fb5432e665 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.es.resx @@ -4584,24 +4584,6 @@ No se puede usar "Authentication={0}" si se ha establecido la propiedad Credential. - - No se pudo adquirir el token de acceso. - - - No se puede conectar con el punto de conexión de la identidad administrada. Compruebe que se está ejecutando en un recurso de Azure que tiene la configuración de identidad. - - - Se intentó obtener el token con la identidad administrada. - - - No se puede conectar con Instance Metadata Service (IMDS). Se omitirá la solicitud al punto de conexión del token de la identidad administrada. - - - Se ha recibido un error no recuperable. - - - Error después de 5 reintentos. - Se ha detectado un tipo inesperado al realizar la deserialización. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx index 3ea703dd8e..8ab8d6ecca 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.fr.resx @@ -4584,24 +4584,6 @@ Impossible d'utiliser « Authentication={0} », si la propriété Credential a été définie. - - Impossible d'acquérir le jeton d'accès. - - - Connexion impossible au point de terminaison d'identité managée. Vérifiez que vous exécutez une ressource Azure avec une identité configurée. - - - Tentative d'obtention d'un jeton à l'aide d'une identité managée. - - - Connexion impossible à Instance Metadata Service (IMDS). Demande ignorée pour le point de terminaison de jeton d'identité managé. - - - Une erreur irrécupérable a été reçue. - - - Échec après 5 tentatives. - Type inattendu détecté pendant la désérialisation. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx index e0dcc33df2..65bf14742d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.it.resx @@ -4584,24 +4584,6 @@ Non è possibile usare 'Authentication={0}' se è stata impostata la proprietà Credential. - - Non è stato possibile acquisire il token di accesso. - - - Non è possibile connettersi all'endpoint dell'identità gestita. Verificare che sia in esecuzione una risorsa di Azure con configurazione dell'identità. - - - È stato eseguito un tentativo di ottenere un token usando l'identità gestita. - - - Non è possibile connettersi al servizio metadati dell'istanza. La richiesta all'endpoint del token dell'identità gestita verrà ignorata. - - - È stato ricevuto un errore irreversibile. - - - L'operazione non è riuscita dopo 5 tentativi. - Tipo imprevisto rilevato durante la deserializzazione. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx index 0a2a429e3c..42a8c9b56a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ja.resx @@ -4584,24 +4584,6 @@ Credential プロパティが設定されている場合は、'Authentication={0}' を使用できません。 - - アクセス トークンを取得できませんでした。 - - - マネージド ID エンドポイントに接続できません。ID セットアップがある Azure リソースで実行していることを確認してください。 - - - マネージド ID を使用してトークンを取得しようとしました。 - - - Instance Metadata Service (IMDS) に接続できません。マネージド ID トークンのエンドポイントに対する要求をスキップしています。 - - - 再試行できないエラーを受信しました。 - - - 5 回の再試行後に失敗しました。 - 逆シリアル化で予期しない型が検出されました。 diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx index 8e21001134..85c72f105f 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ko.resx @@ -4584,24 +4584,6 @@ 자격 증명 속성이 설정된 경우 'Authentication={0}'을(를) 사용할 수 없습니다. - - 액세스 토큰을 획득할 수 없습니다. - - - 관리 ID 엔드포인트에 연결할 수 없습니다. ID가 설정된 Azure 리소스에서 실행 중인지 확인하세요. - - - 관리 ID를 사용하여 토큰을 가져오려 했습니다. - - - IMDS(Instance Metadata Service)에 연결할 수 없습니다. 관리 ID 토큰 엔드포인트에 대한 요청을 건너뜁니다. - - - 다시 시도할 수 없는 오류가 발생했습니다. - - - 5회 다시 시도한 후에 실패했습니다. - 역직렬화 시 예기치 않은 형식이 검색되었습니다. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx index 1a3c634a35..bd242cca25 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.pt-BR.resx @@ -4584,24 +4584,6 @@ Não é possível usar 'Authentication={0}' quando a propriedade Credential está configurada. - - Não foi possível adquirir o token de acesso. - - - Não foi possível se conectar com o ponto de extremidade de Identidade Gerenciada. Verifique se a execução está ocorrendo em um recurso do Azure que tenha configuração de Identidade. - - - Tentativa de obter o token usando a Identidade Gerenciada. - - - Não foi possível se conectar com o IMDS (Serviço de Metadados de Instância). Ignorando a solicitação para o ponto de extremidade do token de Identidade Gerenciada. - - - Um erro sem nova tentativa foi recebido. - - - Falha após cinco novas tentativas. - Tipo inesperado detectado na desserialização. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx index 980ae262af..5151c1ec6a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.ru.resx @@ -4584,24 +4584,6 @@ Невозможно использовать "Authentication={0}", если задано свойство Credential. - - Не удалось получить токен доступа. - - - Не удалось подключиться к конечной точке Управляемого удостоверения. Убедитесь, что вы используете ресурс Azure, для которого настроено удостоверение. - - - Попытка получить токен с помощью Управляемого удостоверения. - - - Не удается подключиться к Службе метаданных экземпляров (IMDS). Запрос будет передан в конечную точку токена Управляемого удостоверения. - - - Получена невоспроизводимая ошибка. - - - Сбой после 5 попыток. - Обнаружен непредвиденный тип при десериализации. diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx index 37c9553533..fb77f3bf7a 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hans.resx @@ -4584,24 +4584,6 @@ 如果设置了 Credential 属性,则无法使用 "Authentication={0}"。 - - 无法获取访问令牌。 - - - 无法连接到托管标识终结点。请检查你运行的 Azure 资源是否设置了标识。 - - - 已尝试使用托管标识来获取令牌。 - - - 无法连接到实例元数据服务(IMDS)。正在跳过对托管标识令牌终结点的请求。 - - - 收到了不可重试的错误。 - - - 5 次重试后失败。 - 反序列化时检测到意外的类型。 diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx index 44828c9e36..0d2aae5c16 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Strings.zh-Hant.resx @@ -4584,24 +4584,6 @@ 如果已設定 Credential 屬性,就無法使用 'Authentication={0}'。 - - 無法取得存取權杖。 - - - 無法連線到受控識別端點。請確認您在具有身分識別設定的 Azure 資源上執行。 - - - 嘗試使用受控識別取得權杖。 - - - 無法連線到 Instance Metadata Service (IMDS) 。正在跳過受控識別權杖端點的要求。 - - - 收到無法重試的錯誤。 - - - 重試 5 次後失敗。 - 還原序列化時偵測到未預期的類型。 diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs similarity index 97% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs index 0ffa1ad5ce..d08db25036 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs @@ -73,9 +73,7 @@ internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSession // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { - // The key derivation function and hash algorithm name are specified when key derivation is performed - ECDiffieHellman clientDHKey = ECDiffieHellman.Create(); - clientDHKey.KeySize = DiffieHellmanKeySize; + ECDiffieHellman clientDHKey = KeyConverter.CreateECDiffieHellman(DiffieHellmanKeySize); byte[] attestationParam = PrepareAttestationParameters(attestationUrl, customData, customDataLength); return new SqlEnclaveAttestationParameters(AzureBasedAttestationProtocolId, attestationParam, clientDHKey); } @@ -528,8 +526,7 @@ private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, byte[] nonce, } // Perform signature verification. The enclave's DiffieHellman public key was signed by the enclave's RSA public key. - RSAParameters rsaParams = KeyConverter.RSAPublicKeyBlobToParams(enclaveRsaPublicKey); - using (RSA rsa = RSA.Create(rsaParams)) + using (RSA rsa = KeyConverter.CreateRSAFromPublicKeyBlob(enclaveRsaPublicKey)) { if (!rsa.VerifyData(enclaveDHInfo.PublicKey, enclaveDHInfo.PublicKeySignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) { @@ -537,9 +534,10 @@ private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, byte[] nonce, } } - ECParameters ecParams = KeyConverter.ECCPublicKeyBlobToParams(enclaveDHInfo.PublicKey); - ECDiffieHellman enclaveDHKey = ECDiffieHellman.Create(ecParams); - return clientDHKey.DeriveKeyFromHash(enclaveDHKey.PublicKey, HashAlgorithmName.SHA256); + using (ECDiffieHellman enclaveDHKey = KeyConverter.CreateECDiffieHellmanFromPublicKeyBlob(enclaveDHInfo.PublicKey)) + { + return KeyConverter.DeriveKey(clientDHKey, enclaveDHKey.PublicKey); + } } #endregion } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.CrossPlatformCrypto.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.CrossPlatformCrypto.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs index 98ba7dd83d..21c91725a5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.CrossPlatformCrypto.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.Crypto.cs @@ -195,7 +195,7 @@ internal byte[] GetSerializedAttestationParameters(SqlEnclaveAttestationParamete throw SQL.NullArgumentInternal(nameof(attestationProtocolInputLengthBytes), nameof(EnclaveDelegate), nameof(GetSerializedAttestationParameters)); } - byte[] clientDHPublicKey = KeyConverter.ECDHPublicKeyToECCKeyBlob(sqlEnclaveAttestationParameters.ClientDiffieHellmanKey.PublicKey); + byte[] clientDHPublicKey = KeyConverter.GetECDiffieHellmanPublicKeyBlob(sqlEnclaveAttestationParameters.ClientDiffieHellmanKey); clientDHPublicKeyLengthBytes = GetUintBytes(enclaveType, clientDHPublicKey.Length, "clientDHPublicKeyLength"); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetStandard.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.NotSupported.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/EnclaveDelegate.NetStandard.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/EnclaveDelegate.NotSupported.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCredential.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCredential.cs similarity index 68% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCredential.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCredential.cs index 5ad7821352..3859b0b000 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCredential.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCredential.cs @@ -7,13 +7,13 @@ namespace Microsoft.Data.SqlClient { - /// + /// public sealed class SqlCredential { string _userId; SecureString _password; - /// + /// public SqlCredential(string userId, SecureString password) { if (userId == null) @@ -45,10 +45,10 @@ public SqlCredential(string userId, SecureString password) _password = password; } - /// + /// public string UserId => _userId; - /// + /// public SecureString Password => _password; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.Crypto.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.Crypto.cs new file mode 100644 index 0000000000..06262b364f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.Crypto.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Security.Cryptography; + +namespace Microsoft.Data.SqlClient +{ + /// + internal class SqlEnclaveAttestationParameters + { + private readonly byte[] _input; + + /// + internal SqlEnclaveAttestationParameters(int protocol, byte[] input, ECDiffieHellman clientDiffieHellmanKey) + { + if (input == null) + { + throw SQL.NullArgumentInConstructorInternal(nameof(input), nameof(SqlEnclaveAttestationParameters)); + } + if (clientDiffieHellmanKey == null) + { + throw SQL.NullArgumentInConstructorInternal(nameof(clientDiffieHellmanKey), nameof(SqlEnclaveAttestationParameters)); + } + + _input = input; + Protocol = protocol; + ClientDiffieHellmanKey = clientDiffieHellmanKey; + } + + /// + internal int Protocol { get; private set; } + + /// + internal ECDiffieHellman ClientDiffieHellmanKey { get; private set; } + + /// + internal byte[] GetInput() + { + // return a new array for safety so the caller cannot mutate the original + if (_input == null) + { + return null; + } + + byte[] output = new byte[_input.Length]; + Buffer.BlockCopy(_input, 0, output, 0, _input.Length); + return output; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NotSupported.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NotSupported.cs new file mode 100644 index 0000000000..0ae67d4a94 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnclaveAttestationParameters.NotSupported.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Data.SqlClient +{ + /// + internal partial class SqlEnclaveAttestationParameters + { + /// + internal int Protocol { get; } + + /// + internal byte[] GetInput() + { + return null; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs similarity index 95% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs index 7a02ba83fd..c87051dd8a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUdtInfo.cs @@ -45,10 +45,11 @@ internal static SqlUdtInfo GetFromType(Type target) internal static SqlUdtInfo TryGetFromType(Type target) { if (s_types2UdtInfo == null) + { s_types2UdtInfo = new Dictionary(); + } - SqlUdtInfo udtAttr = null; - if (!s_types2UdtInfo.TryGetValue(target, out udtAttr)) + if (!s_types2UdtInfo.TryGetValue(target, out SqlUdtInfo udtAttr)) { // query SqlUserDefinedTypeAttribute first time and cache the result object[] attr = target.GetCustomAttributes(typeof(SqlUserDefinedTypeAttribute), false); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs similarity index 93% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs index 663b4a4e19..f71047d965 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs @@ -94,10 +94,7 @@ internal override void GetEnclaveSession(EnclaveSessionParameters enclaveSession // Gets the information that SqlClient subsequently uses to initiate the process of attesting the enclave and to establish a secure session with the enclave. internal override SqlEnclaveAttestationParameters GetAttestationParameters(string attestationUrl, byte[] customData, int customDataLength) { - // The key derivation function and hash algorithm name are specified when key derivation is performed - ECDiffieHellman clientDHKey = ECDiffieHellman.Create(); - clientDHKey.KeySize = DiffieHellmanKeySize; - + ECDiffieHellman clientDHKey = KeyConverter.CreateECDiffieHellman(DiffieHellmanKeySize); return new SqlEnclaveAttestationParameters(VsmHGSProtocolId, new byte[] { }, clientDHKey); } @@ -288,21 +285,19 @@ private X509ChainStatusFlags VerifyHealthReportAgainstRootCertificate(X509Certif private void VerifyEnclaveReportSignature(EnclaveReportPackage enclaveReportPackage, X509Certificate2 healthReportCert) { // Check if report is formatted correctly - UInt32 calculatedSize = Convert.ToUInt32(enclaveReportPackage.PackageHeader.GetSizeInPayload()) + enclaveReportPackage.PackageHeader.SignedStatementSize + enclaveReportPackage.PackageHeader.SignatureSize; + uint calculatedSize = Convert.ToUInt32(enclaveReportPackage.PackageHeader.GetSizeInPayload()) + enclaveReportPackage.PackageHeader.SignedStatementSize + enclaveReportPackage.PackageHeader.SignatureSize; if (calculatedSize != enclaveReportPackage.PackageHeader.PackageSize) { throw new ArgumentException(Strings.VerifyEnclaveReportFormatFailed); } - // IDK_S is contained in healthReport cert public key - using (RSA rsa = healthReportCert.GetRSAPublicKey()) + using (RSA rsa = KeyConverter.GetRSAFromCertificate(healthReportCert)) { if (!rsa.VerifyData(enclaveReportPackage.ReportAsBytes, enclaveReportPackage.SignatureBlob, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)) { throw new ArgumentException(Strings.VerifyEnclaveReportFailed); } - } } @@ -342,7 +337,7 @@ private void VerifyEnclavePolicyProperty(string property, uint actual, uint expe { if (actual < expected) { - string exceptionMessage = String.Format(Strings.VerifyEnclavePolicyFailedFormat, property, actual, expected); + string exceptionMessage = string.Format(Strings.VerifyEnclavePolicyFailedFormat, property, actual, expected); throw new ArgumentException(exceptionMessage); } } @@ -351,8 +346,7 @@ private void VerifyEnclavePolicyProperty(string property, uint actual, uint expe private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, EnclaveDiffieHellmanInfo enclaveDHInfo, ECDiffieHellman clientDHKey) { // Perform signature verification. The enclave's DiffieHellman public key was signed by the enclave's RSA public key. - RSAParameters rsaParams = KeyConverter.RSAPublicKeyBlobToParams(enclavePublicKey.PublicKey); - using (RSA rsa = RSA.Create(rsaParams)) + using (RSA rsa = KeyConverter.CreateRSAFromPublicKeyBlob(enclavePublicKey.PublicKey)) { if (!rsa.VerifyData(enclaveDHInfo.PublicKey, enclaveDHInfo.PublicKeySignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) { @@ -360,9 +354,10 @@ private byte[] GetSharedSecret(EnclavePublicKey enclavePublicKey, EnclaveDiffieH } } - ECParameters ecParams = KeyConverter.ECCPublicKeyBlobToParams(enclaveDHInfo.PublicKey); - ECDiffieHellman enclaveDHKey = ECDiffieHellman.Create(ecParams); - return clientDHKey.DeriveKeyFromHash(enclaveDHKey.PublicKey, HashAlgorithmName.SHA256); + using (ECDiffieHellman ecdh = KeyConverter.CreateECDiffieHellmanFromPublicKeyBlob(enclaveDHInfo.PublicKey)) + { + return KeyConverter.DeriveKey(clientDHKey, ecdh.PublicKey); + } } #endregion } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/Resources/CustomConfigurableRetryLogic.cs b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomConfigurableRetryLogic.cs similarity index 94% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/Resources/CustomConfigurableRetryLogic.cs rename to src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomConfigurableRetryLogic.cs index 4a1d16bbe7..8908857e4a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/Resources/CustomConfigurableRetryLogic.cs +++ b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomConfigurableRetryLogic.cs @@ -6,9 +6,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Data.SqlClient; -namespace ClassLibrary +// These types have been created just to test the configurable retry logic manager in the Manual tests project. +namespace Microsoft.Data.SqlClient.Tests { public class CustomConfigurableRetryLogic { @@ -27,8 +27,8 @@ public class CustomConfigurableRetryLogicEx { public SqlRetryLogicBaseProvider GetDefaultRetry(SqlRetryLogicOption option = null) { + // Trying to get access to a provider inside a custom implementation. SqlRetryLogicBaseProvider provider = new SqlCommand().RetryLogicProvider; - Console.WriteLine(provider.RetryLogic.NumberOfTries); return new CustomRetryLogicProvider(option?.NumberOfTries ?? 1); } } diff --git a/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj new file mode 100644 index 0000000000..16d5397566 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj @@ -0,0 +1,24 @@ + + + ExternalConfigurableRetryLogic + netfx + netcoreapp + $(OS) + true + true + false + Debug;Release;net461-Release;net461-Debug;netcoreapp2.1-Debug;netcoreapp2.1-Release;netcoreapp3.1-Debug;netcoreapp3.1-Release + AnyCPU;x86;x64 + $(ObjFolder)$(Configuration).$(Platform).$(AssemblyName) + $(BinFolder)$(Configuration).$(Platform).$(AssemblyName) + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionRegisterKeyStoreProvider.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionRegisterKeyStoreProvider.cs index 65f875f9ce..d703d4f748 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionRegisterKeyStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionRegisterKeyStoreProvider.cs @@ -22,6 +22,9 @@ public void TestNullDictionary() ArgumentNullException e = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); Assert.Contains(expectedMessage, e.Message); + + e = Assert.Throws(() => connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(customProviders)); + Assert.Contains(expectedMessage, e.Message); } [Fact] @@ -35,6 +38,9 @@ public void TestInvalidProviderName() ArgumentException e = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); Assert.Contains(expectedMessage, e.Message); + + e = Assert.Throws(() => connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(customProviders)); + Assert.Contains(expectedMessage, e.Message); } [Fact] @@ -48,6 +54,9 @@ public void TestNullProviderValue() ArgumentNullException e = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); Assert.Contains(expectedMessage, e.Message); + + e = Assert.Throws(() => connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(customProviders)); + Assert.Contains(expectedMessage, e.Message); } [Fact] @@ -60,6 +69,9 @@ public void TestEmptyProviderName() ArgumentNullException e = Assert.Throws(() => SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders)); Assert.Contains(expectedMessage, e.Message); + + e = Assert.Throws(() => connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(customProviders)); + Assert.Contains(expectedMessage, e.Message); } [Fact] @@ -81,5 +93,47 @@ public void TestCanSetGlobalProvidersOnlyOnce() Utility.ClearSqlConnectionGlobalProviders(); } + + [Fact] + public void TestCanSetInstanceProvidersMoreThanOnce() + { + const string dummyProviderName1 = "DummyProvider1"; + const string dummyProviderName2 = "DummyProvider2"; + const string dummyProviderName3 = "DummyProvider3"; + IDictionary singleKeyStoreProvider = + new Dictionary() + { + {dummyProviderName1, new DummyKeyStoreProvider() } + }; + + IDictionary multipleKeyStoreProviders = + new Dictionary() + { + { dummyProviderName2, new DummyKeyStoreProvider() }, + { dummyProviderName3, new DummyKeyStoreProvider() } + }; + + using (SqlConnection connection = new SqlConnection()) + { + connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(singleKeyStoreProvider); + IReadOnlyDictionary instanceCache = + GetInstanceCacheFromConnection(connection); + Assert.Single(instanceCache); + Assert.True(instanceCache.ContainsKey(dummyProviderName1)); + + connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(multipleKeyStoreProviders); + instanceCache = GetInstanceCacheFromConnection(connection); + Assert.Equal(2, instanceCache.Count); + Assert.True(instanceCache.ContainsKey(dummyProviderName2)); + Assert.True(instanceCache.ContainsKey(dummyProviderName3)); + } + + IReadOnlyDictionary GetInstanceCacheFromConnection(SqlConnection conn) + { + FieldInfo instanceCacheField = conn.GetType().GetField( + "_customColumnEncryptionKeyStoreProviders", BindingFlags.NonPublic | BindingFlags.Instance); + return instanceCacheField.GetValue(conn) as IReadOnlyDictionary; + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index b5cc271605..30e24f8960 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -2160,6 +2160,28 @@ public void TestCustomKeyStoreProviderDuringAeQuery(string connectionString) () => ExecuteQueryThatRequiresCustomKeyStoreProvider(connection)); Assert.Contains(failedToDecryptMessage, ex.Message); Assert.True(ex.InnerException is NotImplementedException); + + // not required provider in instance cache + // it should not fall back to the global cache so the right provider will not be found + connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(notRequiredProvider); + ex = Assert.Throws( + () => ExecuteQueryThatRequiresCustomKeyStoreProvider(connection)); + Assert.Equal(providerNotFoundMessage, ex.Message); + + // required provider in instance cache + // if the instance cache is not empty, it is always checked for the provider. + // => if the provider is found, it must have been retrieved from the instance cache and not the global cache + connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(requiredProvider); + ex = Assert.Throws( + () => ExecuteQueryThatRequiresCustomKeyStoreProvider(connection)); + Assert.Contains(failedToDecryptMessage, ex.Message); + Assert.True(ex.InnerException is NotImplementedException); + + // not required provider will replace the previous entry so required provider will not be found + connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(notRequiredProvider); + ex = Assert.Throws( + () => ExecuteQueryThatRequiresCustomKeyStoreProvider(connection)); + Assert.Equal(providerNotFoundMessage, ex.Message); } void ExecuteQueryThatRequiresCustomKeyStoreProvider(SqlConnection connection) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 51d522bf04..2aefb3fe36 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -18,8 +18,6 @@ using Microsoft.Identity.Client; using Microsoft.Data.SqlClient.TestUtilities; using Xunit; -using Azure.Security.KeyVault.Keys; -using Azure.Identity; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index 79179a01d6..0a1946d7cc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -71,8 +71,10 @@ - + + + @@ -196,9 +198,6 @@ - - - @@ -293,6 +292,7 @@ + @@ -314,18 +314,4 @@ - - - - PreserveNewest - ClassLibrary_CustomConfigurableRetryLogic.dll - - - - - - PreserveNewest - ClassLibrary_CustomConfigurableRetryLogic.dll - - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs index d5ea380f3c..d487b187fb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -388,7 +388,7 @@ public static void ActiveDirectoryManagedIdentityWithInvalidUserIdMustFail(strin AggregateException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); - string expectedMessage = "ManagedIdentityCredential authentication unavailable, the requested identity has not been assigned to this resource."; + string expectedMessage = "ManagedIdentityCredential authentication unavailable"; Assert.Contains(expectedMessage, e.GetBaseException().Message); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/StreamInputParam.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/StreamInputParam.cs index 65e32cca90..3d15b4346b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/StreamInputParam.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/StreamInputParam.cs @@ -457,17 +457,16 @@ private static void ImmediateCancelBin() byte[] data = new byte[dataSize]; rand.NextBytes(data); MemoryStream ms = new MemoryStream(data, false); - cmd.CommandText = "insert into #blobs (Id, blob) values (1, @blob)"; + // Include a delay to allow time for cancellation + cmd.CommandText = "WAITFOR DELAY '00:00:05'; insert into #blobs (Id, blob) values (1, @blob)"; cmd.Parameters.Add("@blob", SqlDbType.VarBinary, dataSize); cmd.Parameters["@blob"].Direction = ParameterDirection.Input; cmd.Parameters["@blob"].Value = ms; - Task t = cmd.ExecuteNonQueryAsync(cts.Token); - if (!t.IsCompleted) - cts.Cancel(); + try { - t.Wait(); - Console.WriteLine("FAIL: Expected AggregateException on Task wait for Cancelled Task! Task Status: " + t.Status); + Task.WaitAll(cmd.ExecuteNonQueryAsync(cts.Token), Task.Run(() => cts.Cancel())); + Console.WriteLine("FAIL: Expected AggregateException on Task wait for Cancelled Task!"); } catch (AggregateException ae) { @@ -504,17 +503,16 @@ private static void ImmediateCancelText() StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000000; i++) sb.Append(i); - cmd.CommandText = "insert into #blobs (Id, blob) values (1, @blob)"; + // Include a delay to allow time for cancellation + cmd.CommandText = "WAITFOR DELAY '00:00:05'; insert into #blobs (Id, blob) values (1, @blob)"; cmd.Parameters.Add("@blob", SqlDbType.VarChar, -1); cmd.Parameters["@blob"].Direction = ParameterDirection.Input; cmd.Parameters["@blob"].Value = new StringReader(sb.ToString()); - Task t = cmd.ExecuteNonQueryAsync(cts.Token); - if (!t.IsCompleted) - cts.Cancel(); + try { - t.Wait(); - Console.WriteLine("FAIL: Expected AggregateException on Task wait for Cancelled Task! Task Status: " + t.Status); + Task.WaitAll(cmd.ExecuteNonQueryAsync(cts.Token), Task.Run(() => cts.Cancel())); + Console.WriteLine("FAIL: Expected AggregateException on Task wait for Cancelled Task!"); } catch (AggregateException ae) { @@ -544,17 +542,16 @@ private static void ImmediateCancelXml() { cmd.CommandText = "create table #blobs (Id int, blob xml)"; cmd.ExecuteNonQuery(); - cmd.CommandText = "insert into #blobs (Id, blob) values (1, @blob)"; + // Include a delay to allow time for cancellation + cmd.CommandText = "WAITFOR DELAY '00:00:05'; insert into #blobs (Id, blob) values (1, @blob)"; cmd.Parameters.Add("@blob", SqlDbType.Xml, -1); cmd.Parameters["@blob"].Direction = ParameterDirection.Input; cmd.Parameters["@blob"].Value = XmlReader.Create(new StringReader(XmlStr)); - Task t = cmd.ExecuteNonQueryAsync(cts.Token); - if (!t.IsCompleted) - cts.Cancel(); + try { - t.Wait(); - Console.WriteLine("FAIL: Expected AggregateException on Task wait for Cancelled Task! Task Status: " + t.Status); + Task.WaitAll(cmd.ExecuteNonQueryAsync(cts.Token), Task.Run(() => cts.Cancel())); + Console.WriteLine("FAIL: Expected AggregateException on Task wait for Cancelled Task!"); } catch (AggregateException ae) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/Resources/netcoreapp/ClassLibrary_CustomConfigurableRetryLogic.dll b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/Resources/netcoreapp/ClassLibrary_CustomConfigurableRetryLogic.dll deleted file mode 100644 index fcc2a8ee94..0000000000 Binary files a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/Resources/netcoreapp/ClassLibrary_CustomConfigurableRetryLogic.dll and /dev/null differ diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/Resources/netfx/ClassLibrary_CustomConfigurableRetryLogic.dll b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/Resources/netfx/ClassLibrary_CustomConfigurableRetryLogic.dll deleted file mode 100644 index 186e676a69..0000000000 Binary files a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/Resources/netfx/ClassLibrary_CustomConfigurableRetryLogic.dll and /dev/null differ diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs index 296a70dfd8..234b60c9e6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlCommandReliabilityTest.cs @@ -506,122 +506,134 @@ public async void RetryExecuteAsyncCancel(string cnnString, SqlRetryLogicBasePro [MemberData(nameof(RetryLogicTestHelper.GetConnectionAndRetryStrategyInvalidCommand), parameters: new object[] { 2 }, MemberType = typeof(RetryLogicTestHelper))] public void ConcurrentExecution(string cnnString, SqlRetryLogicBaseProvider provider) { - var cnnStr = new SqlConnectionStringBuilder(cnnString) { MultipleActiveResultSets = true, ConnectTimeout = 0 }.ConnectionString; int numberOfTries = provider.RetryLogic.NumberOfTries; string query = "SELECT bad command"; int retriesCount = 0; - int concurrentExecution = 5; + int concurrentExecution = 3; provider.Retrying += (s, e) => Interlocked.Increment(ref retriesCount); - using (SqlConnection cnn = new SqlConnection(cnnStr)) + Parallel.For(0, concurrentExecution, + i => { - cnn.Open(); - - Parallel.For(0, concurrentExecution, - i => + using (SqlConnection cnn = new SqlConnection(cnnString)) + using (SqlCommand cmd = cnn.CreateCommand()) { - using (SqlCommand cmd = cnn.CreateCommand()) - { - cmd.RetryLogicProvider = provider; - cmd.CommandText = query; - Assert.Throws(() => cmd.ExecuteScalar()); - } - }); - Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); - - retriesCount = 0; - Parallel.For(0, concurrentExecution, - i => + cnn.Open(); + cmd.RetryLogicProvider = provider; + cmd.CommandText = query; + Assert.Throws(() => cmd.ExecuteScalar()); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + + retriesCount = 0; + Parallel.For(0, concurrentExecution, + i => + { + using (SqlConnection cnn = new SqlConnection(cnnString)) + using (SqlCommand cmd = cnn.CreateCommand()) { - using (SqlCommand cmd = cnn.CreateCommand()) - { - cmd.RetryLogicProvider = provider; - cmd.CommandText = query; - Assert.Throws(() => cmd.ExecuteNonQuery()); - } - }); - Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); - - retriesCount = 0; - Parallel.For(0, concurrentExecution, - i => + cnn.Open(); + cmd.RetryLogicProvider = provider; + cmd.CommandText = query; + Assert.Throws(() => cmd.ExecuteNonQuery()); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + + retriesCount = 0; + Parallel.For(0, concurrentExecution, + i => + { + using (SqlConnection cnn = new SqlConnection(cnnString)) + using (SqlCommand cmd = cnn.CreateCommand()) { - using (SqlCommand cmd = cnn.CreateCommand()) - { - cmd.RetryLogicProvider = provider; - cmd.CommandText = query; - Assert.Throws(() => cmd.ExecuteReader()); - } - }); - Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); - - retriesCount = 0; - Parallel.For(0, concurrentExecution, - i => + cnn.Open(); + cmd.RetryLogicProvider = provider; + cmd.CommandText = query; + Assert.Throws(() => cmd.ExecuteReader()); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + + retriesCount = 0; + Parallel.For(0, concurrentExecution, + i => + { + using (SqlConnection cnn = new SqlConnection(cnnString)) + using (SqlCommand cmd = cnn.CreateCommand()) { - using (SqlCommand cmd = cnn.CreateCommand()) - { - cmd.RetryLogicProvider = provider; - cmd.CommandText = query + " FOR XML AUTO"; - Assert.Throws(() => cmd.ExecuteXmlReader()); - } - }); - Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); - - retriesCount = 0; - Parallel.For(0, concurrentExecution, - i => + cnn.Open(); + cmd.RetryLogicProvider = provider; + cmd.CommandText = query + " FOR XML AUTO"; + Assert.Throws(() => cmd.ExecuteXmlReader()); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + + retriesCount = 0; + Parallel.For(0, concurrentExecution, + i => + { + using (SqlConnection cnn = new SqlConnection(cnnString)) + using (SqlCommand cmd = cnn.CreateCommand()) { - using (SqlCommand cmd = cnn.CreateCommand()) - { - cmd.RetryLogicProvider = provider; - cmd.CommandText = query; - Assert.ThrowsAsync(() => cmd.ExecuteScalarAsync()).Wait(); - } - }); - Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); - - retriesCount = 0; - Parallel.For(0, concurrentExecution, - i => + cnn.Open(); + cmd.RetryLogicProvider = provider; + cmd.CommandText = query; + Assert.ThrowsAsync(() => cmd.ExecuteScalarAsync()).Wait(); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + + retriesCount = 0; + Parallel.For(0, concurrentExecution, + i => + { + using (SqlConnection cnn = new SqlConnection(cnnString)) + using (SqlCommand cmd = cnn.CreateCommand()) { - using (SqlCommand cmd = cnn.CreateCommand()) - { - cmd.RetryLogicProvider = provider; - cmd.CommandText = query; - Assert.ThrowsAsync(() => cmd.ExecuteNonQueryAsync()).Wait(); - } - }); - Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); - - retriesCount = 0; - Parallel.For(0, concurrentExecution, - i => + cnn.Open(); + cmd.RetryLogicProvider = provider; + cmd.CommandText = query; + Assert.ThrowsAsync(() => cmd.ExecuteNonQueryAsync()).Wait(); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + + retriesCount = 0; + Parallel.For(0, concurrentExecution, + i => + { + using (SqlConnection cnn = new SqlConnection(cnnString)) + using (SqlCommand cmd = cnn.CreateCommand()) { - using (SqlCommand cmd = cnn.CreateCommand()) - { - cmd.RetryLogicProvider = provider; - cmd.CommandText = query; - Assert.ThrowsAsync(() => cmd.ExecuteReaderAsync()).Wait(); - } - }); - Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); - - retriesCount = 0; - Parallel.For(0, concurrentExecution, - i => + cnn.Open(); + cmd.RetryLogicProvider = provider; + cmd.CommandText = query; + Assert.ThrowsAsync(() => cmd.ExecuteReaderAsync()).Wait(); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + + // TODO: there is a known issue by ExecuteXmlReaderAsync that should be solved first- issue #44 + /* + retriesCount = 0; + Parallel.For(0, concurrentExecution, + i => + { + using (SqlConnection cnn = new SqlConnection(cnnString)) + using (SqlCommand cmd = cnn.CreateCommand()) { - using (SqlCommand cmd = cnn.CreateCommand()) - { - cmd.RetryLogicProvider = provider; - cmd.CommandText = query + " FOR XML AUTO"; - Assert.ThrowsAsync(() => cmd.ExecuteXmlReaderAsync()).Wait(); - } - }); - Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); - } + cnn.Open(); + cmd.RetryLogicProvider = provider; + cmd.CommandText = query + " FOR XML AUTO"; + Assert.ThrowsAsync(() => cmd.ExecuteXmlReaderAsync()).Wait(); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + */ } - #endregion #region private members diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs index 781e2f7472..ca1bd1ce87 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConfigurationManagerReliabilityTest.cs @@ -72,13 +72,13 @@ public void LoadValidInternalTypesWithoutEnablingSwitch(string method1, string m #region External Functions [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [InlineData("ClassLibrary.StaticCustomConfigurableRetryLogic, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry_static")] - [InlineData("ClassLibrary.StructCustomConfigurableRetryLogic, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry_static")] - [InlineData("ClassLibrary.StructCustomConfigurableRetryLogic, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry")] - [InlineData("ClassLibrary.CustomConfigurableRetryLogic, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry_static")] - [InlineData("ClassLibrary.CustomConfigurableRetryLogic, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry")] - [InlineData("ClassLibrary.CustomConfigurableRetryLogic, ClassLibrary_CustomConfigurableRetryLogic, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "GetDefaultRetry")] - [InlineData("ClassLibrary.CustomConfigurableRetryLogicEx, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry")] + [InlineData("Microsoft.Data.SqlClient.Tests.StaticCustomConfigurableRetryLogic, ExternalConfigurableRetryLogic", "GetDefaultRetry_static")] + [InlineData("Microsoft.Data.SqlClient.Tests.StructCustomConfigurableRetryLogic, ExternalConfigurableRetryLogic", "GetDefaultRetry_static")] + [InlineData("Microsoft.Data.SqlClient.Tests.StructCustomConfigurableRetryLogic, ExternalConfigurableRetryLogic", "GetDefaultRetry")] + [InlineData("Microsoft.Data.SqlClient.Tests.CustomConfigurableRetryLogic, ExternalConfigurableRetryLogic", "GetDefaultRetry_static")] + [InlineData("Microsoft.Data.SqlClient.Tests.CustomConfigurableRetryLogic, ExternalConfigurableRetryLogic", "GetDefaultRetry")] + [InlineData("Microsoft.Data.SqlClient.Tests.CustomConfigurableRetryLogic, ExternalConfigurableRetryLogic, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", "GetDefaultRetry")] + [InlineData("Microsoft.Data.SqlClient.Tests.CustomConfigurableRetryLogicEx, ExternalConfigurableRetryLogic", "GetDefaultRetry")] public void LoadCustomMethod(string typeName, string methodName) { bool switchValue = true; @@ -101,16 +101,16 @@ public void LoadCustomMethod(string typeName, string methodName) } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [InlineData("ClassLibrary.Invalid, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry_static")] - [InlineData("ClassLibrary.Invalid, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry")] - [InlineData("ClassLibrary.CustomConfigurableRetryLogic, ClassLibrary_Invalid", "GetDefaultRetry_static")] - [InlineData("ClassLibrary.StaticCustomConfigurableRetryLogic, ClassLibrary_Invalid", "GetDefaultRetry_static")] - [InlineData("ClassLibrary.StructCustomConfigurableRetryLogic, ClassLibrary_Invalid", "GetDefaultRetry")] + [InlineData("ClassLibrary.Invalid, ExternalConfigurableRetryLogic", "GetDefaultRetry_static")] + [InlineData("ClassLibrary.Invalid, ExternalConfigurableRetryLogic", "GetDefaultRetry")] + [InlineData("Microsoft.Data.SqlClient.Tests.CustomConfigurableRetryLogic, ClassLibrary_Invalid", "GetDefaultRetry_static")] + [InlineData("Microsoft.Data.SqlClient.Tests.StaticCustomConfigurableRetryLogic, ClassLibrary_Invalid", "GetDefaultRetry_static")] + [InlineData("Microsoft.Data.SqlClient.Tests.StructCustomConfigurableRetryLogic, ClassLibrary_Invalid", "GetDefaultRetry")] // Type and method name are case sensitive. - [InlineData("ClassLibrary.StaticCustomConfigurableRetryLogic, ClassLibrary_CustomConfigurableRetryLogic", "GETDEFAULTRETRY_STATIC")] - [InlineData("ClassLibrary.STRUCTCUSTOMCONFIGURABLERETRYLOGIC, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry")] - [InlineData("CLASSLIBRARY.CustomConfigurableRetryLogic, ClassLibrary_CustomConfigurableRetryLogic", "GetDefaultRetry_static")] - [InlineData("ClassLibrary.CustomConfigurableRetryLogic, ClassLibrary_CustomConfigurableRetryLogic", "getdefaultretry")] + [InlineData("Microsoft.Data.SqlClient.Tests.StaticCustomConfigurableRetryLogic, ExternalConfigurableRetryLogic", "GETDEFAULTRETRY_STATIC")] + [InlineData("Microsoft.Data.SqlClient.Tests.STRUCTCUSTOMCONFIGURABLERETRYLOGIC, ExternalConfigurableRetryLogic", "GetDefaultRetry")] + [InlineData("MICROSOFT.DATA.SQLCLIENT.TESTS.CustomConfigurableRetryLogic, ExternalConfigurableRetryLogic", "GetDefaultRetry_static")] + [InlineData("Microsoft.Data.SqlClient.Tests.CustomConfigurableRetryLogic, ExternalConfigurableRetryLogic", "getdefaultretry")] public void LoadInvalidCustomRetryLogicType(string typeName, string methodName) { bool switchValue = true; diff --git a/tools/props/Versions.props b/tools/props/Versions.props index feafea25e0..ec4365798c 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -18,7 +18,7 @@ 1.3.0 - 4.21.1 + 4.22.0 6.8.0 6.8.0 4.5.1 @@ -45,12 +45,11 @@ - [1.2.2,2.0.0) + [1.6.0,2.0.0) [4.0.3,5.0.0) - 1.1.1 3.1.1 5.2.6 15.9.0 diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index b5932d24b9..c03b550e12 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -30,7 +30,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -43,7 +43,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -56,7 +56,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -69,7 +69,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + @@ -82,7 +82,7 @@ When using NuGet 3.x this package requires at least version 3.4. - + diff --git a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec index 212f2cd735..d063fad949 100644 --- a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec +++ b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec @@ -26,17 +26,17 @@ Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyStoreProvider.SqlColumnEncrypti - + - + - +