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 {