Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static class ManagedIdentityAttestationExtensions
{
/// <summary>
/// Enables Credential Guard attestation support for managed identity mTLS Proof-of-Possession flows.
/// This method should be called after <see cref="ManagedIdentityPopExtensions.WithMtlsProofOfPossession"/>.
/// This method should be called after <see cref="ManagedIdentityPopExtensions.WithMtlsProofOfPossession(AcquireTokenForManagedIdentityParameterBuilder)"/>.
/// </summary>
/// <param name="builder">The AcquireTokenForManagedIdentityParameterBuilder instance.</param>
/// <returns>The builder to chain .With methods.</returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public sealed class AcquireTokenForManagedIdentityParameterBuilder :
AbstractManagedIdentityAcquireTokenParameterBuilder<AcquireTokenForManagedIdentityParameterBuilder>
{
private const string MiAttCacheKeyComponent = "mi_att";
private const string MiMinStrengthCacheKeyComponent = "mi_minstrength";
private static readonly Task<string> s_att0 = Task.FromResult("0");
private static readonly Task<string> s_att1 = Task.FromResult("1");

Expand Down Expand Up @@ -130,6 +131,7 @@ private static void ApplyMtlsPopAndAttestation(
AcquireTokenForManagedIdentityParameters acquireTokenForManagedIdentityParameters)
{
acquireTokenForManagedIdentityParameters.IsMtlsPopRequested = acquireTokenCommonParameters.IsMtlsPopRequested;
acquireTokenForManagedIdentityParameters.MtlsPopMinStrength = acquireTokenCommonParameters.MtlsPopMinStrength;
acquireTokenForManagedIdentityParameters.AttestationTokenProvider = acquireTokenCommonParameters.AttestationTokenProvider;

// PoP requests should be partitioned by attestation-support mode.
Expand All @@ -140,6 +142,11 @@ private static void ApplyMtlsPopAndAttestation(

acquireTokenCommonParameters.CacheKeyComponents[MiAttCacheKeyComponent] =
_ => acquireTokenCommonParameters.AttestationTokenProvider != null ? s_att1 : s_att0;

// Partition by the requested minimum binding strength so a higher-floor request
// cannot be satisfied from a cache entry created by a lower/no-floor request.
acquireTokenCommonParameters.CacheKeyComponents[MiMinStrengthCacheKeyComponent] =
_ => Task.FromResult(acquireTokenCommonParameters.MtlsPopMinStrength.ToString());
}
Comment thread
Robbie-Microsoft marked this conversation as resolved.
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ internal class AcquireTokenCommonParameters
public string FmiPathSuffix { get; internal set; }
public string ClientAssertionFmiPath { get; internal set; }
public bool IsMtlsPopRequested { get; set; }

/// <summary>
/// The minimum mTLS binding strength the host must support for the request to succeed.
/// Set via <see cref="AppConfig.PoPOptions.MinStrength"/>. Defaults to
/// <see cref="MtlsBindingStrength.None"/> (no floor).
/// </summary>
public MtlsBindingStrength MtlsPopMinStrength { get; set; } = MtlsBindingStrength.None;
public string ExtraClientAssertionClaims { get; internal set; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.ManagedIdentity;

namespace Microsoft.Identity.Client.ApiConfig.Parameters
Expand All @@ -32,6 +33,12 @@ internal class AcquireTokenForManagedIdentityParameters : IAcquireTokenParameter

public bool IsMtlsPopRequested { get; set; }

/// <summary>
/// The minimum mTLS binding strength the host must support for the request to succeed.
/// Defaults to <see cref="MtlsBindingStrength.None"/> (no floor).
/// </summary>
public MtlsBindingStrength MtlsPopMinStrength { get; set; } = MtlsBindingStrength.None;

internal X509Certificate2 MtlsCertificate { get; set; }

/// <summary>
Expand All @@ -54,6 +61,7 @@ public void LogParameters(ILoggerAdapter logger)
ClientClaims: {!string.IsNullOrEmpty(ClientClaims)}
RevokedTokenHash: {!string.IsNullOrEmpty(RevokedTokenHash)}
IsMtlsPopRequested: {IsMtlsPopRequested}
MtlsPopMinStrength: {MtlsPopMinStrength}
""");
}
}
Expand Down
33 changes: 33 additions & 0 deletions src/client/Microsoft.Identity.Client/AppConfig/PoPOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Identity.Client.AppConfig
{
/// <summary>
/// Options that control mTLS Proof-of-Possession (PoP) token acquisition. This type is the
/// extensibility point for PoP-related knobs so future settings can be added without growing
/// the builder surface.
/// </summary>
/// <remarks>
/// This type is shared by managed identity and confidential client mTLS Proof-of-Possession
/// scenarios.
/// </remarks>
public class PoPOptions
{
/// <summary>
/// Gets or sets the minimum binding strength the host must be able to produce for the
/// request to succeed. This is a <b>floor assertion</b>, not a downgrade selector: MSAL
/// always uses the host's maximum binding strength, and the request fails when the host
/// cannot meet this floor.
/// </summary>
/// <value>
/// The minimum required <see cref="MtlsBindingStrength"/>. The default is
/// <see cref="MtlsBindingStrength.None"/>, which imposes no floor and behaves identically
/// to the parameterless mTLS PoP request. When set to a value greater than
/// <see cref="MtlsBindingStrength.None"/>, the request fails with
/// <see cref="MsalError.MinStrengthNotMet"/> if the host's maximum binding strength is
/// lower than this value.
/// </value>
public MtlsBindingStrength MinStrength { get; set; } = MtlsBindingStrength.None;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public X509Certificate2 MtlsCertificate
}

public bool IsMtlsPopRequested => _commonParameters.IsMtlsPopRequested;
public MtlsBindingStrength MtlsPopMinStrength => _commonParameters.MtlsPopMinStrength;
public bool? SendOfflineAccessScope => _commonParameters.SendOfflineAccessScope;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ internal async Task<ManagedIdentityResponse> SendTokenRequestForManagedIdentityA
AcquireTokenForManagedIdentityParameters parameters,
CancellationToken cancellationToken)
{
// Enforce a minimum binding strength floor when requested via PoPOptions.MinStrength.
// The token-request path normally routes mTLS PoP straight to IMDSv2 without probing,
// so explicitly run discovery to learn the host's maximum binding strength and fail
// fast if the host cannot meet the required floor.
if (parameters.MtlsPopMinStrength > MtlsBindingStrength.None)
{
ManagedIdentityDiscoveryResult discovery =
await GetManagedIdentityCapabilitiesAsync(requestContext, cancellationToken).ConfigureAwait(false);

if (discovery.MaxSupportedBindingStrength < parameters.MtlsPopMinStrength)
{
throw new MsalClientException(
MsalError.MinStrengthNotMet,
MsalErrorMessage.MinStrengthNotMet(discovery.MaxSupportedBindingStrength, parameters.MtlsPopMinStrength));
}
}

AbstractManagedIdentity msi = await GetOrSelectManagedIdentitySourceAsync(requestContext, parameters.IsMtlsPopRequested, cancellationToken).ConfigureAwait(false);
return await msi.AuthenticateAsync(parameters, cancellationToken).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.PlatformsCommon.Shared;

namespace Microsoft.Identity.Client
Expand All @@ -20,6 +21,31 @@ public static class ManagedIdentityPopExtensions
public static AcquireTokenForManagedIdentityParameterBuilder WithMtlsProofOfPossession(
this AcquireTokenForManagedIdentityParameterBuilder builder)
{
return builder.WithMtlsProofOfPossession(new PoPOptions());
}

/// <summary>
/// Enables mTLS Proof-of-Possession for managed identity token acquisition with additional
/// PoP options, such as a minimum required binding strength.
/// When attestation is required (KeyGuard scenarios), use the Msal.KeyAttestation package
/// and call .WithAttestationSupport() after this method.
/// </summary>
/// <param name="builder">The AcquireTokenForManagedIdentityParameterBuilder instance.</param>
/// <param name="options">
/// PoP options controlling the request. Use <see cref="PoPOptions.MinStrength"/> to require
/// a minimum host binding strength; the request fails with
/// <see cref="MsalError.MinStrengthNotMet"/> when the host cannot meet the floor.
/// </param>
/// <returns>The builder to chain .With methods.</returns>
public static AcquireTokenForManagedIdentityParameterBuilder WithMtlsProofOfPossession(
this AcquireTokenForManagedIdentityParameterBuilder builder,
PoPOptions options)
{
if (options == null)
{
throw new System.ArgumentNullException(nameof(options));
}
Comment thread
Robbie-Microsoft marked this conversation as resolved.

if (!DesktopOsHelper.IsWindows())
{
throw new MsalClientException(
Expand All @@ -33,6 +59,7 @@ public static AcquireTokenForManagedIdentityParameterBuilder WithMtlsProofOfPoss
MsalErrorMessage.MtlsNotSupportedForManagedIdentityMessage);
#else
builder.CommonParameters.IsMtlsPopRequested = true;
builder.CommonParameters.MtlsPopMinStrength = options.MinStrength;
return builder;
#endif
}
Expand Down
10 changes: 10 additions & 0 deletions src/client/Microsoft.Identity.Client/MsalError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,16 @@ public static class MsalError
/// </summary>
public const string MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1";

/// <summary>
/// <para>What happened?</para> A minimum mTLS binding strength was requested via
/// <c>PoPOptions.MinStrength</c>, but the current host cannot produce a key binding that
/// meets the required floor.
/// <para>Mitigation:</para> Deploy on a host capable of the required binding strength
/// (for example, a Trusted Launch or Confidential VM for KeyGuard), or lower the requested
/// minimum strength.
/// </summary>
public const string MinStrengthNotMet = "min_strength_not_met";

/// <summary>
/// All managed identity sources are unavailable.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/client/Microsoft.Identity.Client/MsalErrorMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,9 @@ public static string InvalidTokenProviderResponseValue(string invalidValueName)
public const string InvalidCertificate = "The certificate received from the Imds server is invalid.";
public const string CannotSwitchBetweenImdsVersionsForPreview = "ImdsV2 is currently experimental - A Bearer token has already been received; Please restart the application to receive a mTLS PoP token.";
public const string MtlsPopTokenNotSupportedinImdsV1 = "mTLS Proof of Possession with managed identity is currently in private preview and is not supported on this VM. Ensure you're running on a supported VM image.";

public static string MinStrengthNotMet(object actualStrength, object requiredStrength) =>
$"The host's maximum mTLS binding strength '{actualStrength}' does not meet the required minimum binding strength '{requiredStrength}' specified via PoPOptions.MinStrength.";
public const string ManagedIdentityAllSourcesUnavailable = "All Managed Identity sources are unavailable.";
public const string SendCertificateOverMtlsRequiresCertificate =
"CertificateOptions.SendCertificateOverMtls is only valid with a certificate-based credential " +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Microsoft.Identity.Client.AppConfig.PoPOptions
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.get -> Microsoft.Identity.Client.AppConfig.MtlsBindingStrength
Microsoft.Identity.Client.AppConfig.PoPOptions.MinStrength.set -> void
Microsoft.Identity.Client.AppConfig.PoPOptions.PoPOptions() -> void
const Microsoft.Identity.Client.MsalError.MinStrengthNotMet = "min_strength_not_met" -> string
static Microsoft.Identity.Client.ManagedIdentityPopExtensions.WithMtlsProofOfPossession(this Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder builder, Microsoft.Identity.Client.AppConfig.PoPOptions options) -> Microsoft.Identity.Client.AcquireTokenForManagedIdentityParameterBuilder
Loading
Loading