From 4d0a12760a26ce8e72a9174b6268faa5915d1620 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Tue, 24 Feb 2026 19:52:05 -0400 Subject: [PATCH] Task 42666: Verify Azure DLL via public key token - Consolidated strong name signing into Directory.Build.props. - Added a conditional compilation constant for strong name signing. - Added public key token check when SqlClient loads the Azure assembly. - Added logging related to Azure assembly loading. - Added explicit check for .NET runtime. - Added a way to define whatever conditional compilation constants we want on the command-line. --- .../steps/compound-build-csproj-step.yml | 9 +-- src/Directory.Build.props | 17 +++++ .../Abstractions/src/Abstractions.csproj | 9 +-- .../Azure/src/Azure.csproj | 9 +-- .../Logging/src/Logging.csproj | 9 +-- ...waysEncrypted.AzureKeyVaultProvider.csproj | 14 +--- .../ref/Microsoft.Data.SqlClient.csproj | 8 -- .../src/Microsoft.Data.SqlClient.csproj | 9 +-- .../netfx/ref/Microsoft.Data.SqlClient.csproj | 8 -- .../netfx/src/Microsoft.Data.SqlClient.csproj | 9 +-- .../ref/Microsoft.Data.SqlClient.csproj | 8 -- .../src/Microsoft.Data.SqlClient.csproj | 9 +-- .../SqlAuthenticationProviderManager.cs | 76 ++++++++++++++++--- .../Microsoft.SqlServer.Server.csproj | 8 -- 14 files changed, 95 insertions(+), 107 deletions(-) diff --git a/eng/pipelines/steps/compound-build-csproj-step.yml b/eng/pipelines/steps/compound-build-csproj-step.yml index 152ec7d308..7d0d04c079 100644 --- a/eng/pipelines/steps/compound-build-csproj-step.yml +++ b/eng/pipelines/steps/compound-build-csproj-step.yml @@ -4,11 +4,10 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -# Generic build step for csproj-based Extension packages (Logging, Abstractions, Azure). Each -# project uses a build.proj target that runs Build only and produces assemblies within -# $(BUILD_OUTPUT). Downstream ESRP DLL signing must locate the assemblies within $(BUILD_OUTPUT) -# for all target frameworks that the csproj targets. NuGet packaging is done separately via -# compound-pack-csproj-step.yml after DLL signing. +# Generic build step for csproj-based packages. Each project uses a build.proj target that runs +# Build only and produces assemblies within $(BUILD_OUTPUT). Downstream ESRP DLL signing must +# locate the assemblies within $(BUILD_OUTPUT) for all target frameworks that the csproj targets. +# NuGet packaging is done separately via compound-pack-csproj-step.yml after DLL signing. parameters: # The MSBuild build target in build.proj (e.g. BuildLogging, BuildAbstractions, diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 48e94629a2..5152ce6065 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -61,6 +61,12 @@ false + + + + $(DefineConstants);$(UserDefinedConstants) + + $(DefineConstants);ENCLAVE_SIMULATOR @@ -109,6 +115,17 @@ + + + + + true + $(SigningKeyPath) + + + $(DefineConstants);STRONG_NAME_SIGNING + + portable diff --git a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj index 685bf5531e..f7a1470f02 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Abstractions/src/Abstractions.csproj @@ -14,14 +14,7 @@ netstandard2.0 - - - - - true - $(SigningKeyPath) - - + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj index 3b44906a9d..31f999079d 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Azure/src/Azure.csproj @@ -14,14 +14,7 @@ netstandard2.0;net462 - - - - - true - $(SigningKeyPath) - - + diff --git a/src/Microsoft.Data.SqlClient.Extensions/Logging/src/Logging.csproj b/src/Microsoft.Data.SqlClient.Extensions/Logging/src/Logging.csproj index b249ac9da5..9682b33c23 100644 --- a/src/Microsoft.Data.SqlClient.Extensions/Logging/src/Logging.csproj +++ b/src/Microsoft.Data.SqlClient.Extensions/Logging/src/Logging.csproj @@ -14,14 +14,7 @@ netstandard2.0 - - - - - true - $(SigningKeyPath) - - + diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj index 8354cfd014..560d12474b 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj @@ -15,17 +15,9 @@ - - true - true - - - - - - - true - $(SigningKeyPath) + + true + true diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index 026b686276..f3d5bdc844 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -15,14 +15,6 @@ AnyCPU;x64;x86 - - - - - true - $(SigningKeyPath) - - 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 7aab6ea6ee..3ad83ed492 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -20,14 +20,7 @@ false - - - - - true - $(SigningKeyPath) - - + diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index ffcd8a8cff..6765e009db 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -10,14 +10,6 @@ Debug;Release - - - - - true - $(SigningKeyPath) - - 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 f77227b0ad..cdd54bd244 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -17,14 +17,7 @@ false - - - - - true - $(SigningKeyPath) - - + <_Parameter1>UnitTests diff --git a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj index 401b0c7d8b..41b4df05ab 100644 --- a/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/ref/Microsoft.Data.SqlClient.csproj @@ -11,14 +11,6 @@ - - - - - true - $(SigningKeyPath) - - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index fb54bc02ef..2e9045ee83 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -14,14 +14,7 @@ - - - - - true - $(SigningKeyPath) - - + <_Parameter1>UnitTests diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs index 1bf236c348..0e5e7c5201 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAuthenticationProviderManager.cs @@ -26,6 +26,13 @@ internal sealed class SqlAuthenticationProviderManager private const string ActiveDirectoryDefault = "active directory default"; private const string ActiveDirectoryWorkloadIdentity = "active directory workload identity"; + // The name of our Azure extension assembly. + private const string azureAssemblyName = "Microsoft.Data.SqlClient.Extensions.Azure"; + + // The public key token of our Azure extension assembly, used to avoid loading imposter + // assemblies. + private static readonly byte[] azurePublicKeyToken = [ 0x23, 0xec, 0x7f, 0xc2, 0xd6, 0xea, 0xa4, 0xa5 ]; + static SqlAuthenticationProviderManager() { SqlAuthenticationProviderConfigurationSection? configurationSection = null; @@ -48,30 +55,77 @@ static SqlAuthenticationProviderManager() Instance = new SqlAuthenticationProviderManager(configurationSection); - // If our Azure extensions package is present, use its - // authentication provider as our default. - const string assemblyName = "Microsoft.Data.SqlClient.Extensions.Azure"; - + // If our Azure extensions package is present, use its authentication provider as our + // default. try { // Try to load our Azure extension. - var assembly = Assembly.Load(assemblyName); + #if STRONG_NAME_SIGNING + + // When strong-name signing is enabled, build a fully-qualified AssemblyName + // that includes the expected public key token. + + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Attempting to load Azure extension assembly={azureAssemblyName} with " + + "expected public key token=" + + BitConverter.ToString(azurePublicKeyToken).Replace("-", "")); + + var qualifiedName = new AssemblyName(azureAssemblyName); + qualifiedName.SetPublicKeyToken(azurePublicKeyToken); + + // The .NET Framework runtime will enforce the token during binding, causing Load() + // to throw. This prevents an untrusted assembly from being loaded and having its + // module initializers run. This will throw if the public key token doesn't match. + // + // The .NET runtime ignores the public key token and will happily load any assembly + // with the same simple name. + // + var assembly = Assembly.Load(qualifiedName); + + #if NET + // For the .NET runtime, we will check the public key token ourselves. + // + // Note that a null assembly is handled below. + if (assembly is not null) + { + byte[]? actualToken = assembly.GetName().GetPublicKeyToken(); + + if (actualToken is null || !actualToken.AsSpan().SequenceEqual(azurePublicKeyToken)) + { + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Azure extension assembly={assembly.GetName()} has an " + + "unexpected public key token; " + + "no default Active Directory provider installed"); + return; + } + } + #endif + + #else + + SqlClientEventSource.Log.TryTraceEvent( + nameof(SqlAuthenticationProviderManager) + + $": Attempting to load Azure extension assembly={azureAssemblyName} without " + + "strong name verification; ensure this assembly is from a trusted source"); + + var assembly = Assembly.Load(azureAssemblyName); + + #endif if (assembly is null) { SqlClientEventSource.Log.TryTraceEvent( nameof(SqlAuthenticationProviderManager) + - $": Azure extension assembly={assemblyName} not found; " + + $": Azure extension assembly={azureAssemblyName} not found; " + "no default Active Directory provider installed"); return; } - // TODO(https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/39845): - // Verify the assembly is signed by us? - SqlClientEventSource.Log.TryTraceEvent( nameof(SqlAuthenticationProviderManager) + - $": Azure extension assembly={assemblyName} found; " + + $": Azure extension assembly={assembly.GetName()} found; " + "attempting to set as default provider for all Active " + "Directory authentication methods"); @@ -147,7 +201,7 @@ ex is TargetInvocationException || { SqlClientEventSource.Log.TryTraceEvent( nameof(SqlAuthenticationProviderManager) + - $": Azure extension assembly={assemblyName} not found or " + + $": Azure extension assembly={azureAssemblyName} not found or " + "not usable; no default provider installed; " + $"{ex.GetType().Name}: {ex.Message}"); } diff --git a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj index 877e841ce5..7689684421 100644 --- a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj +++ b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj @@ -70,14 +70,6 @@ Microsoft.SqlServer.Server.Format - - - - - true - $(SigningKeyPath) - -