diff --git a/LibsAndSamples.sln b/LibsAndSamples.sln
index b2d234b638..aa205da089 100644
--- a/LibsAndSamples.sln
+++ b/LibsAndSamples.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
-VisualStudioVersion = 18.0.11217.181 d18.0
+VisualStudioVersion = 18.0.11217.181
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{9B0B5396-4D95-4C15-82ED-DC22B5A3123F}"
ProjectSection(SolutionItems) = preProject
@@ -192,8 +192,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MacMauiAppWithBroker", "tes
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MacConsoleAppWithBroker", "tests\devapps\MacConsoleAppWithBroker\MacConsoleAppWithBroker.csproj", "{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Identity.Client.MtlsPop", "src\client\Microsoft.Identity.Client.MtlsPop\Microsoft.Identity.Client.MtlsPop.csproj", "{3E1C29E5-6E67-D9B2-28DF-649A609937A2}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinUI3PackagedSampleApp", "tests\devapps\WinUI3PackagedSampleApp\WinUI3PackagedSampleApp.csproj", "{CE282240-0806-EB91-87E4-D791DC86DEE8}"
EndProject
Global
@@ -1948,48 +1946,6 @@ Global
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}.Release|x64.Build.0 = Release|Any CPU
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}.Release|x86.ActiveCfg = Release|Any CPU
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0}.Release|x86.Build.0 = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|Any CPU.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|Any CPU.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|ARM.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|ARM.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|ARM64.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|ARM64.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|iPhone.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|iPhone.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|iPhoneSimulator.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|x64.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|x64.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|x86.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug + MobileApps|x86.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|ARM.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|ARM64.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|ARM64.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|iPhone.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|iPhone.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|x64.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|x64.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|x86.ActiveCfg = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Debug|x86.Build.0 = Debug|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|Any CPU.Build.0 = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|ARM.ActiveCfg = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|ARM.Build.0 = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|ARM64.ActiveCfg = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|ARM64.Build.0 = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|iPhone.ActiveCfg = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|iPhone.Build.0 = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|x64.ActiveCfg = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|x64.Build.0 = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|x86.ActiveCfg = Release|Any CPU
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2}.Release|x86.Build.0 = Release|Any CPU
{CE282240-0806-EB91-87E4-D791DC86DEE8}.Debug + MobileApps|Any CPU.ActiveCfg = Debug|x64
{CE282240-0806-EB91-87E4-D791DC86DEE8}.Debug + MobileApps|Any CPU.Build.0 = Debug|x64
{CE282240-0806-EB91-87E4-D791DC86DEE8}.Debug + MobileApps|ARM.ActiveCfg = Debug|x64
@@ -2089,7 +2045,6 @@ Global
{97995B86-AA0F-3AF9-DA40-85A6263E4391} = {9B0B5396-4D95-4C15-82ED-DC22B5A3123F}
{AEF6BB00-931F-4638-955D-24D735625C34} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
{DBD18BC8-72E4-47D4-BD79-8DEBD9F2C0D0} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
- {3E1C29E5-6E67-D9B2-28DF-649A609937A2} = {1A37FD75-94E9-4D6F-953A-0DABBD7B49E9}
{CE282240-0806-EB91-87E4-D791DC86DEE8} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
diff --git a/prototype/MsiV2DemoApp/MsiV2DemoApp.csproj b/prototype/MsiV2DemoApp/MsiV2DemoApp.csproj
index e8ae98ddef..db688e9072 100644
--- a/prototype/MsiV2DemoApp/MsiV2DemoApp.csproj
+++ b/prototype/MsiV2DemoApp/MsiV2DemoApp.csproj
@@ -9,7 +9,6 @@
-
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationClient.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationClient.cs
similarity index 95%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationClient.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/AttestationClient.cs
index c0c8faf588..b71df2723d 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationClient.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationClient.cs
@@ -1,17 +1,17 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
-namespace Microsoft.Identity.Client.MtlsPop.Attestation
+namespace Microsoft.Identity.Client.KeyAttestation
{
///
/// Managed façade for AttestationClientLib.dll. Holds initialization state,
/// does ref-count hygiene on , and returns a JWT.
///
- internal sealed class AttestationClient : IDisposable
+ public sealed class AttestationClient : IDisposable
{
private bool _initialized;
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationClientLib.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationClientLib.cs
similarity index 92%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationClientLib.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/AttestationClientLib.cs
index df84387024..247e7af182 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationClientLib.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationClientLib.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using Microsoft.Win32.SafeHandles;
@@ -6,7 +6,7 @@
using System.IO;
using System.Runtime.InteropServices;
-namespace Microsoft.Identity.Client.MtlsPop.Attestation
+namespace Microsoft.Identity.Client.KeyAttestation
{
internal static class AttestationClientLib
{
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationErrors.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationErrors.cs
similarity index 89%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationErrors.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/AttestationErrors.cs
index 0c47ceed76..8074af02ce 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationErrors.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationErrors.cs
@@ -1,11 +1,11 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
-namespace Microsoft.Identity.Client.MtlsPop.Attestation
+namespace Microsoft.Identity.Client.KeyAttestation
{
internal static class AttestationErrors
{
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationLogger.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationLogger.cs
similarity index 91%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationLogger.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/AttestationLogger.cs
index a59f601bdf..22e37e9d93 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationLogger.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationLogger.cs
@@ -1,11 +1,11 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
-namespace Microsoft.Identity.Client.MtlsPop.Attestation
+namespace Microsoft.Identity.Client.KeyAttestation
{
internal static class AttestationLogger
{
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationResult.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationResult.cs
similarity index 87%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationResult.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/AttestationResult.cs
index 79b3f647f5..e78408d496 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationResult.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationResult.cs
@@ -1,7 +1,7 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
-namespace Microsoft.Identity.Client.MtlsPop.Attestation
+namespace Microsoft.Identity.Client.KeyAttestation
{
///
/// AttestationResult is the result of an attestation operation.
@@ -20,7 +20,7 @@ namespace Microsoft.Identity.Client.MtlsPop.Attestation
/// expression, e.g.: var updated = result with { Jwt = newJwt };
/// The netstandard2.0 target relies on the IsExternalInit shim (see IsExternalInit.cs) to enable 'init'.
///
- internal sealed record AttestationResult(
+ public sealed record AttestationResult(
AttestationStatus Status,
string Jwt,
int NativeErrorCode,
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationResultErrorCode.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationResultErrorCode.cs
similarity index 95%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationResultErrorCode.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/AttestationResultErrorCode.cs
index 4f02375292..0b15eab683 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationResultErrorCode.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationResultErrorCode.cs
@@ -1,11 +1,11 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
-namespace Microsoft.Identity.Client.MtlsPop.Attestation
+namespace Microsoft.Identity.Client.KeyAttestation
{
///
/// Error codes returned by AttestationClientLib.dll.
@@ -35,7 +35,7 @@ internal enum AttestationResultErrorCode
/// The attestation enclave rejected the supplied evidence (policy or signature failure).
ERRORATTESTATIONFAILED = -6,
- /// libcurl reported “couldn’t send” (DNS resolution, TLS handshake, or socket error).
+ /// libcurl reported "couldn't send" (DNS resolution, TLS handshake, or socket error).
ERRORSENDINGCURLREQUESTFAILED = -7,
/// One or more input parameters passed to the native API were invalid or null.
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationStatus.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationStatus.cs
similarity index 84%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationStatus.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/AttestationStatus.cs
index ff20df8aa9..b49abadb00 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/AttestationStatus.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/AttestationStatus.cs
@@ -1,16 +1,16 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Text;
-namespace Microsoft.Identity.Client.MtlsPop.Attestation
+namespace Microsoft.Identity.Client.KeyAttestation
{
///
/// High-level outcome categories returned by .
///
- internal enum AttestationStatus
+ public enum AttestationStatus
{
/// Everything succeeded; is populated.
Success = 0,
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/IsExternalInit.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/IsExternalInit.cs
similarity index 71%
rename from src/client/Microsoft.Identity.Client.MtlsPop/IsExternalInit.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/IsExternalInit.cs
index dfb6a17acc..6eeb15edba 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/IsExternalInit.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/IsExternalInit.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#if NETSTANDARD
diff --git a/src/client/Microsoft.Identity.Client.KeyAttestation/ManagedIdentityAttestationExtensions.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/ManagedIdentityAttestationExtensions.cs
new file mode 100644
index 0000000000..958bad28cb
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/ManagedIdentityAttestationExtensions.cs
@@ -0,0 +1,67 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Identity.Client;
+using Microsoft.Identity.Client.ManagedIdentity;
+
+namespace Microsoft.Identity.Client.KeyAttestation
+{
+ ///
+ /// Extension methods for enabling KeyGuard attestation support in managed identity mTLS PoP flows.
+ ///
+ public static class ManagedIdentityAttestationExtensions
+ {
+ ///
+ /// Enables KeyGuard attestation support for managed identity mTLS Proof-of-Possession flows.
+ /// This method should be called after .
+ ///
+ /// The AcquireTokenForManagedIdentityParameterBuilder instance.
+ /// The builder to chain .With methods.
+ public static AcquireTokenForManagedIdentityParameterBuilder WithAttestationSupport(
+ this AcquireTokenForManagedIdentityParameterBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ // Register the attestation token provider
+ return builder.WithAttestationProviderForTests(async (req, ct) =>
+ {
+ // Get the caller-provided KeyGuard/CNG handle
+ var keyHandle = req.KeyHandle;
+
+ if (keyHandle == null)
+ {
+ throw new MsalClientException(
+ "attestation_key_handle_missing",
+ "KeyHandle is required for attestation but was not provided.");
+ }
+
+ // Call the native interop via PopKeyAttestor
+ AttestationResult attestationResult = await PopKeyAttestor.AttestKeyGuardAsync(
+ req.AttestationEndpoint.AbsoluteUri,
+ keyHandle,
+ req.ClientId ?? string.Empty,
+ ct).ConfigureAwait(false);
+
+ // Map to MSAL's internal response
+ if (attestationResult != null &&
+ attestationResult.Status == AttestationStatus.Success &&
+ !string.IsNullOrWhiteSpace(attestationResult.Jwt))
+ {
+ return new AttestationTokenResponse { AttestationToken = attestationResult.Jwt };
+ }
+
+ throw new MsalClientException(
+ "attestation_failure",
+ $"Key Attestation failed " +
+ $"(status={attestationResult?.Status}, " +
+ $"code={attestationResult?.NativeErrorCode}). {attestationResult?.ErrorMessage}");
+ });
+ }
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Microsoft.Identity.Client.MtlsPop.csproj b/src/client/Microsoft.Identity.Client.KeyAttestation/Microsoft.Identity.Client.KeyAttestation.csproj
similarity index 74%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Microsoft.Identity.Client.MtlsPop.csproj
rename to src/client/Microsoft.Identity.Client.KeyAttestation/Microsoft.Identity.Client.KeyAttestation.csproj
index 281eaacf00..68a9cd6c12 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Microsoft.Identity.Client.MtlsPop.csproj
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/Microsoft.Identity.Client.KeyAttestation.csproj
@@ -1,4 +1,4 @@
-
+
@@ -19,20 +19,14 @@
$(MicrosoftIdentityClientVersion)-preview
- MSAL.NET extension for managed identity proof-of-possession flows
+ MSAL.NET extension for KeyGuard attestation support
- This package contains binaries needed to use managed identity proof-of-possession (MTLS PoP) flows in applications using MSAL.NET.
+ This package contains binaries needed to enable KeyGuard attestation in managed identity proof-of-possession (mTLS PoP) flows using MSAL.NET.
- Microsoft Authentication Library Managed Identity MSAL Proof-of-Possession
+ Microsoft Authentication Library Managed Identity MSAL KeyGuard Attestation Proof-of-Possession
Microsoft Authentication Library
-
-
-
-
-
@@ -42,6 +36,11 @@
+
+
+ $(NoWarn);RS0016;RS0017;RS0036;RS0041
+
+
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/NativeDiagnostics.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/NativeDiagnostics.cs
similarity index 86%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Attestation/NativeDiagnostics.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/NativeDiagnostics.cs
index 9482039c8e..f47b85bae3 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/NativeDiagnostics.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/NativeDiagnostics.cs
@@ -1,11 +1,11 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.ComponentModel;
using System.IO;
-namespace Microsoft.Identity.Client.MtlsPop.Attestation
+namespace Microsoft.Identity.Client.KeyAttestation
{
internal static class NativeDiagnostics
{
@@ -38,7 +38,7 @@ internal static string ProbeNativeDll()
return $"Unable to load {NativeDll}: {ex.Message}";
}
- // success – unload and return null (meaning “no error”)
+ // success – unload and return null (meaning "no error")
WindowsDllLoader.Free(h);
return null;
}
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/PopKeyAttestor.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/PopKeyAttestor.cs
similarity index 92%
rename from src/client/Microsoft.Identity.Client.MtlsPop/PopKeyAttestor.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/PopKeyAttestor.cs
index f855041bce..2ddcefda3a 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/PopKeyAttestor.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/PopKeyAttestor.cs
@@ -1,20 +1,19 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
-using Microsoft.Identity.Client.MtlsPop.Attestation;
using Microsoft.Win32.SafeHandles;
-namespace Microsoft.Identity.Client.MtlsPop
+namespace Microsoft.Identity.Client.KeyAttestation
{
///
/// Static facade for attesting a KeyGuard/CNG key and getting a JWT back.
/// Key discovery / rotation is the caller's responsibility.
///
- internal static class PopKeyAttestor
+ public static class PopKeyAttestor
{
///
/// Asynchronously attests a KeyGuard/CNG key with the remote attestation service and returns a JWT.
diff --git a/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/net8.0/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/net8.0/PublicAPI.Shipped.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/net8.0/PublicAPI.Unshipped.txt
new file mode 100644
index 0000000000..c24fbe5105
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/net8.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,24 @@
+Microsoft.Identity.Client.KeyAttestation.AttestationClient
+Microsoft.Identity.Client.KeyAttestation.AttestationClient.AttestationClient() -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationClient.Attest(string! endpoint, Microsoft.Win32.SafeHandles.SafeNCryptKeyHandle! keyHandle, string! clientId) -> Microsoft.Identity.Client.KeyAttestation.AttestationResult!
+Microsoft.Identity.Client.KeyAttestation.AttestationClient.Dispose() -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.AttestationResult(Microsoft.Identity.Client.KeyAttestation.AttestationStatus Status, string! Jwt, int NativeErrorCode, string! ErrorMessage) -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.ErrorMessage.get -> string!
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.ErrorMessage.init -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.Jwt.get -> string!
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.Jwt.init -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.NativeErrorCode.get -> int
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.NativeErrorCode.init -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.Status.get -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.Status.init -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.Exception = 4 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.NativeError = 1 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.NotInitialized = 3 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.Success = 0 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.TokenEmpty = 2 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.ManagedIdentityAttestationExtensions
+Microsoft.Identity.Client.KeyAttestation.PopKeyAttestor
+static Microsoft.Identity.Client.KeyAttestation.ManagedIdentityAttestationExtensions.WithAttestationSupport(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder! builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder!
+static Microsoft.Identity.Client.KeyAttestation.PopKeyAttestor.AttestKeyGuardAsync(string! endpoint, System.Runtime.InteropServices.SafeHandle! keyHandle, string! clientId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
diff --git a/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
new file mode 100644
index 0000000000..c24fbe5105
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,24 @@
+Microsoft.Identity.Client.KeyAttestation.AttestationClient
+Microsoft.Identity.Client.KeyAttestation.AttestationClient.AttestationClient() -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationClient.Attest(string! endpoint, Microsoft.Win32.SafeHandles.SafeNCryptKeyHandle! keyHandle, string! clientId) -> Microsoft.Identity.Client.KeyAttestation.AttestationResult!
+Microsoft.Identity.Client.KeyAttestation.AttestationClient.Dispose() -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.AttestationResult(Microsoft.Identity.Client.KeyAttestation.AttestationStatus Status, string! Jwt, int NativeErrorCode, string! ErrorMessage) -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.ErrorMessage.get -> string!
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.ErrorMessage.init -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.Jwt.get -> string!
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.Jwt.init -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.NativeErrorCode.get -> int
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.NativeErrorCode.init -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.Status.get -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationResult.Status.init -> void
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.Exception = 4 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.NativeError = 1 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.NotInitialized = 3 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.Success = 0 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.AttestationStatus.TokenEmpty = 2 -> Microsoft.Identity.Client.KeyAttestation.AttestationStatus
+Microsoft.Identity.Client.KeyAttestation.ManagedIdentityAttestationExtensions
+Microsoft.Identity.Client.KeyAttestation.PopKeyAttestor
+static Microsoft.Identity.Client.KeyAttestation.ManagedIdentityAttestationExtensions.WithAttestationSupport(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder! builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder!
+static Microsoft.Identity.Client.KeyAttestation.PopKeyAttestor.AttestKeyGuardAsync(string! endpoint, System.Runtime.InteropServices.SafeHandle! keyHandle, string! clientId, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/WindowsDllLoader.cs b/src/client/Microsoft.Identity.Client.KeyAttestation/WindowsDllLoader.cs
similarity index 91%
rename from src/client/Microsoft.Identity.Client.MtlsPop/Attestation/WindowsDllLoader.cs
rename to src/client/Microsoft.Identity.Client.KeyAttestation/WindowsDllLoader.cs
index aaee9eadb2..3084769f06 100644
--- a/src/client/Microsoft.Identity.Client.MtlsPop/Attestation/WindowsDllLoader.cs
+++ b/src/client/Microsoft.Identity.Client.KeyAttestation/WindowsDllLoader.cs
@@ -1,11 +1,12 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.ComponentModel;
-using System.Runtime.InteropServices;
+using System.Runtime.InteropServices;
+using Microsoft.Identity.Client;
-namespace Microsoft.Identity.Client.MtlsPop.Attestation
+namespace Microsoft.Identity.Client.KeyAttestation
{
///
/// Windows‑only helper that loads a native DLL from an absolute path.
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/ManagedIdentityPopExtensions.cs b/src/client/Microsoft.Identity.Client.MtlsPop/ManagedIdentityPopExtensions.cs
deleted file mode 100644
index 742df7b00f..0000000000
--- a/src/client/Microsoft.Identity.Client.MtlsPop/ManagedIdentityPopExtensions.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License.
-
-using System.Runtime.InteropServices;
-using Microsoft.Identity.Client.MtlsPop.Attestation;
-using Microsoft.Identity.Client.PlatformsCommon.Shared;
-
-namespace Microsoft.Identity.Client.MtlsPop
-{
- ///
- /// Registers the mTLS PoP attestation runtime (interop) by installing a provider
- /// function into MSAL's internal config.
- ///
- public static class ManagedIdentityPopExtensions
- {
- ///
- /// App-level registration: tells MSAL how to obtain a KeyGuard/CNG handle
- /// and perform attestation to get the JWT needed for mTLS PoP.
- ///
- public static AcquireTokenForManagedIdentityParameterBuilder WithMtlsProofOfPossession(
- this AcquireTokenForManagedIdentityParameterBuilder builder)
- {
- void MtlsNotSupportedForManagedIdentity(string message)
- {
- throw new MsalClientException(
- MsalError.MtlsNotSupportedForManagedIdentity,
- message);
- }
-
- if (!DesktopOsHelper.IsWindows())
- {
- MtlsNotSupportedForManagedIdentity(MsalErrorMessage.MtlsNotSupportedForNonWindowsMessage);
- }
-
-#if NET462
- MtlsNotSupportedForManagedIdentity(MsalErrorMessage.MtlsNotSupportedForManagedIdentityMessage);
-#endif
-
- builder.CommonParameters.IsMtlsPopRequested = true;
- AddRuntimeSupport(builder);
- return builder;
- }
-
- ///
- /// Adds the runtime support by registering the attestation function.
- ///
- ///
- ///
- private static void AddRuntimeSupport(
- AcquireTokenForManagedIdentityParameterBuilder builder)
- {
- // Register the "runtime" function that PoP operation will invoke.
- builder.CommonParameters.AttestationTokenProvider =
- async (req, ct) =>
- {
- // 1) Get the caller-provided KeyGuard/CNG handle
- SafeHandle keyHandle = req.KeyHandle;
-
- // 2) Call the native interop via PopKeyAttestor
- AttestationResult attestationResult = await PopKeyAttestor.AttestKeyGuardAsync(
- req.AttestationEndpoint.AbsoluteUri, // expects string
- keyHandle,
- req.ClientId ?? string.Empty,
- ct).ConfigureAwait(false);
-
- // 3) Map to MSAL's internal response
- if (attestationResult != null &&
- attestationResult.Status == AttestationStatus.Success &&
- !string.IsNullOrWhiteSpace(attestationResult.Jwt))
- {
- return new ManagedIdentity.AttestationTokenResponse { AttestationToken = attestationResult.Jwt };
- }
-
- throw new MsalClientException(
- "attestation_failure",
- $"Key Attestation failed " +
- $"(status={attestationResult?.Status}, " +
- $"code={attestationResult?.NativeErrorCode}). {attestationResult?.ErrorMessage}");
- };
- }
- }
-}
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/net8.0/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/net8.0/PublicAPI.Shipped.txt
deleted file mode 100644
index a068838189..0000000000
--- a/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/net8.0/PublicAPI.Shipped.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Microsoft.Identity.Client.MtlsPop.ManagedIdentityPopExtensions
-static Microsoft.Identity.Client.MtlsPop.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
-
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/net8.0/PublicAPI.Unshipped.txt
deleted file mode 100644
index 5f282702bb..0000000000
--- a/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/net8.0/PublicAPI.Unshipped.txt
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/netstandard2.0/PublicAPI.Shipped.txt
deleted file mode 100644
index a068838189..0000000000
--- a/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/netstandard2.0/PublicAPI.Shipped.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Microsoft.Identity.Client.MtlsPop.ManagedIdentityPopExtensions
-static Microsoft.Identity.Client.MtlsPop.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
-
diff --git a/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
deleted file mode 100644
index 5f282702bb..0000000000
--- a/src/client/Microsoft.Identity.Client.MtlsPop/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityPopExtensions.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityPopExtensions.cs
new file mode 100644
index 0000000000..61e220fb4b
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/ManagedIdentityPopExtensions.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.Identity.Client.PlatformsCommon.Shared;
+
+namespace Microsoft.Identity.Client
+{
+ ///
+ /// Extension methods for enabling mTLS Proof-of-Possession in managed identity flows.
+ ///
+ public static class ManagedIdentityPopExtensions
+ {
+ ///
+ /// Enables mTLS Proof-of-Possession for managed identity token acquisition.
+ /// When attestation is required (KeyGuard scenarios), use the Msal.KeyAttestation package
+ /// and call .WithAttestationSupport() after this method.
+ ///
+ /// The AcquireTokenForManagedIdentityParameterBuilder instance.
+ /// The builder to chain .With methods.
+ public static AcquireTokenForManagedIdentityParameterBuilder WithMtlsProofOfPossession(
+ this AcquireTokenForManagedIdentityParameterBuilder builder)
+ {
+ if (!DesktopOsHelper.IsWindows())
+ {
+ throw new MsalClientException(
+ MsalError.MtlsNotSupportedForManagedIdentity,
+ MsalErrorMessage.MtlsNotSupportedForNonWindowsMessage);
+ }
+
+#if NET462
+ throw new MsalClientException(
+ MsalError.MtlsNotSupportedForManagedIdentity,
+ MsalErrorMessage.MtlsNotSupportedForManagedIdentityMessage);
+#else
+ builder.CommonParameters.IsMtlsPopRequested = true;
+ return builder;
+#endif
+ }
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequestBody.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequestBody.cs
index 64b27ccc45..33a88671b6 100644
--- a/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequestBody.cs
+++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/CertificateRequestBody.cs
@@ -3,6 +3,7 @@
#if SUPPORTS_SYSTEM_TEXT_JSON
using JsonProperty = System.Text.Json.Serialization.JsonPropertyNameAttribute;
+ using JsonIgnore = System.Text.Json.Serialization.JsonIgnoreAttribute;
#else
using Microsoft.Identity.Json;
#endif
@@ -14,7 +15,12 @@ internal class CertificateRequestBody
[JsonProperty("csr")]
public string Csr { get; set; }
+#if SUPPORTS_SYSTEM_TEXT_JSON
[JsonProperty("attestation_token")]
+ [JsonIgnore(Condition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull)]
+#else
+ [JsonProperty("attestation_token", NullValueHandling = NullValueHandling.Ignore)]
+#endif
public string AttestationToken { get; set; }
public static bool IsNullOrEmpty(CertificateRequestBody certificateRequestBody)
diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs
index 114b6bbc01..1fb18efaee 100644
--- a/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs
+++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/V2/ImdsV2ManagedIdentitySource.cs
@@ -181,14 +181,8 @@ private async Task ExecuteCertificateRequestAsync(
{ OAuth2Header.XMsCorrelationId, _requestContext.CorrelationId.ToString() }
};
- if (managedIdentityKeyInfo.Type != ManagedIdentityKeyType.KeyGuard)
- {
- throw new MsalClientException(
- "mtls_pop_requires_keyguard",
- "[ImdsV2] mTLS Proof-of-Possession requires a KeyGuard-backed key. Enable KeyGuard or use a KeyGuard-supported environment.");
- }
-
- // Ask helper for JWT only for KeyGuard keys
+ // Attempt attestation only for KeyGuard keys when provider is available
+ // For non-KeyGuard keys (Hardware, InMemory), proceed with non-attested flow
string attestationJwt = string.Empty;
var attestationUri = new Uri(attestationEndpoint);
@@ -200,6 +194,10 @@ private async Task ExecuteCertificateRequestAsync(
managedIdentityKeyInfo,
_requestContext.UserCancellationToken).ConfigureAwait(false);
}
+ else
+ {
+ _requestContext.Logger.Info($"[ImdsV2] Using {managedIdentityKeyInfo.Type} key. Proceeding with non-attested mTLS PoP flow.");
+ }
var certificateRequestBody = new CertificateRequestBody()
{
@@ -261,6 +259,22 @@ protected override async Task CreateRequestAsync(string
{
CsrMetadata csrMetadata = await GetCsrMetadataAsync(_requestContext).ConfigureAwait(false);
+ // Validate that mTLS PoP requires KeyGuard - fail fast before network calls
+ if (_isMtlsPopRequested)
+ {
+ IManagedIdentityKeyProvider keyProvider = _requestContext.ServiceBundle.PlatformProxy.ManagedIdentityKeyProvider;
+ ManagedIdentityKeyInfo keyInfo = await keyProvider
+ .GetOrCreateKeyAsync(_requestContext.Logger, _requestContext.UserCancellationToken)
+ .ConfigureAwait(false);
+
+ if (keyInfo.Type != ManagedIdentityKeyType.KeyGuard)
+ {
+ throw new MsalClientException(
+ "mtls_pop_requires_keyguard",
+ $"[ImdsV2] mTLS Proof-of-Possession requires KeyGuard keys. Current key type: {keyInfo.Type}");
+ }
+ }
+
string certCacheKey = _requestContext.ServiceBundle.Config.ClientId;
MtlsBindingInfo mtlsBinding = await GetOrCreateMtlsBindingAsync(
@@ -357,9 +371,19 @@ private async Task GetAttestationJwtAsync(
ManagedIdentityKeyInfo keyInfo,
CancellationToken cancellationToken)
{
- // Provider is a local dependency; missing provider is a client error
+ // Get the attestation provider if available
var provider = _requestContext.AttestationTokenProvider;
+ // If no provider is configured:
+ // - For KeyGuard keys: proceed with ephemeral keys (non-attested flow)
+ // - For non-KeyGuard keys: proceed with non-attested flow
+ // This allows mTLS PoP to work without the attestation package
+ if (provider == null)
+ {
+ _requestContext.Logger.Info("[ImdsV2] No attestation provider configured. Proceeding with non-attested flow.");
+ return null; // Null attestation token indicates non-attested flow (field will be omitted from JSON)
+ }
+
// KeyGuard requires RSACng on Windows
if (keyInfo.Type == ManagedIdentityKeyType.KeyGuard &&
keyInfo.Key is not System.Security.Cryptography.RSACng rsaCng)
diff --git a/src/client/Microsoft.Identity.Client/Properties/InternalsVisibleTo.cs b/src/client/Microsoft.Identity.Client/Properties/InternalsVisibleTo.cs
index d6c67f8270..36547b51ab 100644
--- a/src/client/Microsoft.Identity.Client/Properties/InternalsVisibleTo.cs
+++ b/src/client/Microsoft.Identity.Client/Properties/InternalsVisibleTo.cs
@@ -7,7 +7,7 @@
[assembly: InternalsVisibleTo("Microsoft.Identity.Client.Desktop" + KeyTokens.MSAL)]
[assembly: InternalsVisibleTo("Microsoft.Identity.Client.Desktop.WinUI3" + KeyTokens.MSAL)]
[assembly: InternalsVisibleTo("Microsoft.Identity.Client.Broker" + KeyTokens.MSAL)]
-[assembly: InternalsVisibleTo("Microsoft.Identity.Client.MtlsPop" + KeyTokens.MSAL)]
+[assembly: InternalsVisibleTo("Microsoft.Identity.Client.KeyAttestation" + KeyTokens.MSAL)]
[assembly: InternalsVisibleTo("Microsoft.Identity.Test.Unit" + KeyTokens.MSAL)]
[assembly: InternalsVisibleTo("Microsoft.Identity.Test.Common" + KeyTokens.MSAL)]
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
index 3241ccd9cd..92182ccae2 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt
@@ -1,2 +1,4 @@
const Microsoft.Identity.Client.MsalError.ManagedIdentityAllSourcesUnavailable = "managed_identity_all_sources_unavailable" -> string
Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.ManagedIdentityPopExtensions
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
index 3241ccd9cd..92182ccae2 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt
@@ -1,2 +1,4 @@
const Microsoft.Identity.Client.MsalError.ManagedIdentityAllSourcesUnavailable = "managed_identity_all_sources_unavailable" -> string
Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.ManagedIdentityPopExtensions
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
index 3241ccd9cd..014e0c99ad 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt
@@ -1,2 +1,4 @@
const Microsoft.Identity.Client.MsalError.ManagedIdentityAllSourcesUnavailable = "managed_identity_all_sources_unavailable" -> string
Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.ManagedIdentityPopExtensions
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder! builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder!
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
index 3241ccd9cd..014e0c99ad 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt
@@ -1,2 +1,4 @@
const Microsoft.Identity.Client.MsalError.ManagedIdentityAllSourcesUnavailable = "managed_identity_all_sources_unavailable" -> string
Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.ManagedIdentityPopExtensions
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder! builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder!
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
index 3241ccd9cd..92182ccae2 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt
@@ -1,2 +1,4 @@
const Microsoft.Identity.Client.MsalError.ManagedIdentityAllSourcesUnavailable = "managed_identity_all_sources_unavailable" -> string
Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.ManagedIdentityPopExtensions
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
index 3241ccd9cd..92182ccae2 100644
--- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1,2 +1,4 @@
const Microsoft.Identity.Client.MsalError.ManagedIdentityAllSourcesUnavailable = "managed_identity_all_sources_unavailable" -> string
Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task
+Microsoft.Identity.Client.ManagedIdentityPopExtensions
+static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
diff --git a/tests/Microsoft.Identity.Test.E2e/KeyGuardAttestationTests.cs b/tests/Microsoft.Identity.Test.E2e/KeyGuardAttestationTests.cs
index 4542e9c210..cbbae36056 100644
--- a/tests/Microsoft.Identity.Test.E2e/KeyGuardAttestationTests.cs
+++ b/tests/Microsoft.Identity.Test.E2e/KeyGuardAttestationTests.cs
@@ -28,13 +28,10 @@ If any prerequisite is missing (e.g., VBS off, endpoint unset, native DLL absent
the test exits early with Assert.Inconclusive instead of failing the overall build.
*/
-using Microsoft.Identity.Client.MtlsPop.Attestation;
-using Microsoft.Identity.Test.Common.Core.Helpers;
+using Microsoft.Identity.Client.KeyAttestation;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
-using System.Runtime.InteropServices;
using System.Security.Cryptography;
-using Microsoft.Identity.Client.MtlsPop;
using System.Threading.Tasks;
using System.Threading;
diff --git a/tests/Microsoft.Identity.Test.E2e/ManagedIdentityAzureArcTests.cs b/tests/Microsoft.Identity.Test.E2e/ManagedIdentityAzureArcTests.cs
index bf541b8396..2c99cf63e2 100644
--- a/tests/Microsoft.Identity.Test.E2e/ManagedIdentityAzureArcTests.cs
+++ b/tests/Microsoft.Identity.Test.E2e/ManagedIdentityAzureArcTests.cs
@@ -5,7 +5,6 @@
using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System;
using System.Threading.Tasks;
namespace Microsoft.Identity.Test.E2E
diff --git a/tests/Microsoft.Identity.Test.E2e/ManagedIdentityImdsTests.cs b/tests/Microsoft.Identity.Test.E2e/ManagedIdentityImdsTests.cs
index d0b149c232..1947b1d083 100644
--- a/tests/Microsoft.Identity.Test.E2e/ManagedIdentityImdsTests.cs
+++ b/tests/Microsoft.Identity.Test.E2e/ManagedIdentityImdsTests.cs
@@ -3,7 +3,6 @@
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.AppConfig;
-using Microsoft.Identity.Client.MtlsPop;
using Microsoft.Identity.Test.Common.Core.Helpers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
diff --git a/tests/Microsoft.Identity.Test.E2e/Microsoft.Identity.Test.E2E.MSI.csproj b/tests/Microsoft.Identity.Test.E2e/Microsoft.Identity.Test.E2E.MSI.csproj
index ae7c12399c..377a72a0f7 100644
--- a/tests/Microsoft.Identity.Test.E2e/Microsoft.Identity.Test.E2E.MSI.csproj
+++ b/tests/Microsoft.Identity.Test.E2e/Microsoft.Identity.Test.E2E.MSI.csproj
@@ -8,7 +8,7 @@
-
+
diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs
index 7e61d7d908..a3f6cc5b42 100644
--- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs
+++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ImdsV2Tests.cs
@@ -15,7 +15,6 @@
using Microsoft.Identity.Client.ManagedIdentity;
using Microsoft.Identity.Client.ManagedIdentity.KeyProviders;
using Microsoft.Identity.Client.ManagedIdentity.V2;
-using Microsoft.Identity.Client.MtlsPop;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.PlatformsCommon.Shared;
using Microsoft.Identity.Test.Common.Core.Helpers;
@@ -642,7 +641,7 @@ public void AttachPrivateKeyToCert_NullPrivateKey_ThrowsArgumentNullException()
#region Attestation Tests
[TestMethod]
- public async Task MtlsPop_AttestationProviderMissing_ThrowsClientException()
+ public async Task MtlsPop_NoAttestationProvider_UsesNonAttestedFlow()
{
using (new EnvVariableContext())
using (var httpManager = new MockHttpManager())
@@ -651,17 +650,19 @@ public async Task MtlsPop_AttestationProviderMissing_ThrowsClientException()
var mi = await CreateManagedIdentityAsync(httpManager, managedIdentityKeyType: ManagedIdentityKeyType.KeyGuard).ConfigureAwait(false);
- // CreateManagedIdentityAsync does a probe; Add one more CSR response for the actual acquire.
- httpManager.AddMockHandler(MockHelpers.MockCsrResponse());
+ // Add mocks for successful non-attested flow (CSR + issuecredential + token)
+ // Note: No attestation token in the certificate request
+ AddMocksToGetEntraToken(httpManager);
- var ex = await Assert.ThrowsExceptionAsync(async () =>
- await mi.AcquireTokenForManagedIdentity(ManagedIdentityTests.Resource)
- .WithMtlsProofOfPossession()
- // Intentionally DO NOT call .WithAttestationProviderForTests(...)
- .ExecuteAsync().ConfigureAwait(false)
- ).ConfigureAwait(false);
+ var result = await mi.AcquireTokenForManagedIdentity(ManagedIdentityTests.Resource)
+ .WithMtlsProofOfPossession()
+ // Intentionally DO NOT call .WithAttestationProviderForTests(...)
+ .ExecuteAsync().ConfigureAwait(false);
- Assert.AreEqual("attestation_failure", ex.ErrorCode);
+ Assert.IsNotNull(result);
+ Assert.AreEqual(MTLSPoP, result.TokenType, "Should get mTLS PoP token without attestation provider");
+ Assert.IsNotNull(result.BindingCertificate, "Should have binding certificate even without attestation");
+ Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
}
}
diff --git a/tests/Microsoft.Identity.Test.Unit/Microsoft.Identity.Test.Unit.csproj b/tests/Microsoft.Identity.Test.Unit/Microsoft.Identity.Test.Unit.csproj
index a6f295c6d2..eb6a842e0f 100644
--- a/tests/Microsoft.Identity.Test.Unit/Microsoft.Identity.Test.Unit.csproj
+++ b/tests/Microsoft.Identity.Test.Unit/Microsoft.Identity.Test.Unit.csproj
@@ -16,7 +16,6 @@
-
{3433eb33-114a-4db7-bc57-14f17f55da3c}
Microsoft.Identity.Client
diff --git a/tests/devapps/Managed Identity apps/ManagedIdentityAppVM/ManagedIdentityAppVM.csproj b/tests/devapps/Managed Identity apps/ManagedIdentityAppVM/ManagedIdentityAppVM.csproj
index ea3a7b6aec..150d7f869c 100644
--- a/tests/devapps/Managed Identity apps/ManagedIdentityAppVM/ManagedIdentityAppVM.csproj
+++ b/tests/devapps/Managed Identity apps/ManagedIdentityAppVM/ManagedIdentityAppVM.csproj
@@ -8,7 +8,6 @@
-
diff --git a/tests/devapps/Managed Identity apps/ManagedIdentityAppVM/Program.cs b/tests/devapps/Managed Identity apps/ManagedIdentityAppVM/Program.cs
index 68eade0c26..48175a3e70 100644
--- a/tests/devapps/Managed Identity apps/ManagedIdentityAppVM/Program.cs
+++ b/tests/devapps/Managed Identity apps/ManagedIdentityAppVM/Program.cs
@@ -9,7 +9,6 @@
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.AppConfig;
using Microsoft.IdentityModel.Abstractions;
-using Microsoft.Identity.Client.MtlsPop;
internal class Program
{