Skip to content

Fix #3894: make Microsoft.Identity.Client.KeyAttestation a conditional (modern .NET only) dependency#3922

Merged
gladjohn merged 1 commit into
masterfrom
gladjohn/fix-3894-conditional-keyattestation
Jul 3, 2026
Merged

Fix #3894: make Microsoft.Identity.Client.KeyAttestation a conditional (modern .NET only) dependency#3922
gladjohn merged 1 commit into
masterfrom
gladjohn/fix-3894-conditional-keyattestation

Conversation

@gladjohn

@gladjohn gladjohn commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #3894.

Microsoft.Identity.Web 4.11.0+ cannot be installed into classic (packages.config) .NET Framework projects. Restore fails with:

Could not install package 'Microsoft.Azure.Security.KeyGuardAttestation 1.1.5'.
You are trying to install this package into a project that targets '.NETFramework,Version=v4.8',
but the package does not contain any assembly references or content files that are compatible with that framework.

This PR makes the Microsoft.Identity.Client.KeyAttestation dependency conditional on modern .NET (.NETCoreApp), so .NET Framework consumers no longer drag in the native-only attestation package, while modern .NET keeps full mTLS PoP + key-attestation functionality.

Root cause

Microsoft.Identity.Web.Certificateless targeted only netstandard2.0 and referenced Microsoft.Identity.Client.KeyAttestation unconditionally:

  • A single-TFM (netstandard2.0) package exposes one NuGet dependency group that applies to every compatible consumer — including .NET Framework 4.6.2 / 4.7.2 / 4.8.
  • Microsoft.Identity.Client.KeyAttestation transitively depends on Microsoft.Azure.Security.KeyGuardAttestation, which is a native (C++) package:
    • nuspec: <tags>Native native</tags>, no <dependencies>, no frameworkAssemblies
    • contents: only build/native/** (headers + .lib/.dll) and runtimes/win-x64/native/**
    • no managed lib/ assembly and no .NET Framework / netstandard-compatible asset

Dependency chain on .NET Framework:

Microsoft.Identity.Web
  -> Microsoft.Identity.Web.TokenAcquisition
       -> Microsoft.Identity.Web.Certificateless   (netstandard2.0 asset)
            -> Microsoft.Identity.Client.KeyAttestation
                 -> Microsoft.Azure.Security.KeyGuardAttestation   [native-only, no netfx assets]

Why only some users hit it

Consumer project style Behavior
Classic .NET Framework (packages.config) — ASP.NET "Web Site", MVC Web App Strict per-package TFM compatibility check rejects the native-only KeyGuardAttestation. This is the reported error.
SDK-style (PackageReference) Tolerates native/runtime-only packages, restore succeeds.

This is why the two reporters (a net48 "website" and a net472 "MVC Web Application", both packages.config) fail, while SDK-style projects do not.

The fix

Key attestation (via KeyGuard) is only available/meaningful on modern .NET Confidential VMs, so the dependency is restricted to .NETCoreApp targets:

  1. Microsoft.Identity.Web.Certificateless.csproj
    • Multi-target netstandard2.0;net8.0 (was netstandard2.0 only). This is the minimum needed: net9/net10 consumers resolve the net8 asset (which carries attestation), so no additional modern TFMs are required.
    • Move the Microsoft.Identity.Client.KeyAttestation PackageReference into an ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'".
  2. ManagedIdentityClientAssertion.cs / TokenAcquisition.cs
    • Guard using Microsoft.Identity.Client.KeyAttestation; and the .WithAttestationSupport() call with #if NETCOREAPP.
    • .WithMtlsProofOfPossession() is base MSAL (all TFMs) and is intentionally kept: the mTLS PoP path is unchanged; only the attestation add-on is compiled out on non-.NETCoreApp targets.
  3. AzureIdentityForKubernetesClientAssertion.cs
    • File.ReadAllText(_filePath!) — a pre-existing latent null-deref that only surfaced once the project began compiling for net8.0 (whose reference assemblies annotate the parameter as non-null, and the repo builds with TreatWarningsAsErrors). Matches the existing _filePath! usage a few lines below.

Design notes

  • Why not stay netstandard2.0-only? For .WithAttestationSupport() to actually run on modern .NET it must be compiled into a build where NETCOREAPP is defined. A netstandard2.0-only project never defines it, so the attestation code would be compiled out for everyone. Multi-targeting is the only NuGet mechanism for a per-consumer-framework-conditional dependency, and ManagedIdentityClientAssertion is a public type so it can't be relocated to another project without an API break.
  • Consistency: every sibling project (Certificate, TokenCache, Diagnostics, TokenAcquisition) already multi-targets; Certificateless was the only netstandard2.0-only one. The .NETCoreApp conditional-dependency pattern already exists in 5 csprojs.
  • #if NETCOREAPP (not #if NET8_0_OR_GREATER) so the guard exactly mirrors the MSBuild '$(TargetFrameworkIdentifier)' == '.NETCoreApp' condition.
  • No PlatformNotSupportedException. Public API (GetSignedAssertionWithBindingAsync, SupportsTokenBinding) is identical across all TFMs; on .NET Framework the binding path simply omits the attestation add-on.
  • netstandard2.0 remains the single .NET Framework-facing asset — net462/net472/net48 consumers resolve to it and get a clean graph. No new net462/net472 assets are introduced.

Validation (all local)

Package dependency groups (dotnet pack -> inspect .nuspec):

TFM asset KeyAttestation dependency
net8.0 present
netstandard2.0 removed (was present before)

End-to-end restore matrix (packed all 6 IdWeb packages to a local feed, restored real consumer projects):

Scenario KeyAttestation in graph KeyGuardAttestation in graph
Published 4.12.0, net48 (before) present present -> breaks packages.config
This PR, net48 none none
This PR, net8.0 present present (attestation preserved)

Build (0 warnings, 0 errors): Certificateless across netstandard2.0;net8.0; TokenAcquisition and the full dependency chain across net462;net472;netstandard2.0;net8.0;net9.0;net10.0 — the latter confirms net9/net10 consumers correctly bind Certificateless's net8 asset.

Unit tests (Microsoft.Identity.Web.Test, 0 failures):

TFM Passed
net8.0 990
net9.0 999
net10.0 1018
net472 44

Risk / compatibility

  • No public API changes (PublicAPI analyzer files unchanged; surface identical across TFMs).
  • .NET Framework never had working KeyGuard attestation (native lib is win-x64 only and the package could not even install on packages.config), so nothing is lost there.
  • Modern .NET behavior is unchanged (attestation still referenced and called).

Files changed

  • src/Microsoft.Identity.Web.Certificateless/Microsoft.Identity.Web.Certificateless.csproj
  • src/Microsoft.Identity.Web.Certificateless/ManagedIdentityClientAssertion.cs
  • src/Microsoft.Identity.Web.Certificateless/AzureIdentityForKubernetesClientAssertion.cs
  • src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
  • changelog.md

@gladjohn
gladjohn requested a review from a team as a code owner July 3, 2026 14:31
…l (modern .NET only) dependency

Microsoft.Identity.Web.Certificateless targeted only netstandard2.0 with an unconditional reference to Microsoft.Identity.Client.KeyAttestation, which transitively pulls the native-only Microsoft.Azure.Security.KeyGuardAttestation package. That package ships no .NET Framework/netstandard-compatible managed assets, so classic packages.config .NET Framework projects fail to install Microsoft.Identity.Web 4.11.0+. Multi-target Certificateless (netstandard2.0;net8.0 - net9/net10 consumers resolve the net8 asset) and restrict the KeyAttestation dependency to .NETCoreApp targets, guarding the using and .WithAttestationSupport() calls with #if NETCOREAPP. .NET Framework consumers now use the clean netstandard2.0 asset; modern .NET keeps full mTLS PoP + attestation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gladjohn
gladjohn force-pushed the gladjohn/fix-3894-conditional-keyattestation branch from 8ba10f8 to a38dc0a Compare July 3, 2026 14:45
@gladjohn
gladjohn merged commit 1253aee into master Jul 3, 2026
4 checks passed
@gladjohn
gladjohn deleted the gladjohn/fix-3894-conditional-keyattestation branch July 3, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't update using nuget

3 participants