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)
-
-