diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/AbstractConfidentialClientAcquireTokenParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AbstractConfidentialClientAcquireTokenParameterBuilder.cs index e27c606c10..fc40a59e14 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/AbstractConfidentialClientAcquireTokenParameterBuilder.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/AbstractConfidentialClientAcquireTokenParameterBuilder.cs @@ -79,6 +79,7 @@ protected override void Validate() /// This is an experimental API. The method signature may change in the future without involving a major version upgrade. /// /// + [EditorBrowsable(EditorBrowsableState.Never)] // Soft deprecate public T WithProofOfPossession(PoPAuthenticationConfiguration popAuthenticationConfiguration) { ValidateUseOfExperimentalFeature(); @@ -89,5 +90,29 @@ public T WithProofOfPossession(PoPAuthenticationConfiguration popAuthenticationC return this as T; } + + /// + /// Modifies the token acquisition request so that the acquired token is a Proof-of-Possession token (PoP), rather than a Bearer token. + /// PoP tokens are similar to Bearer tokens, but are bound to the HTTP request and to a cryptographic key, which MSAL can manage on Windows. + /// See https://aka.ms/msal-net-pop + /// + /// Configuration properties used to construct a Proof-of-Possession request. + /// The builder. + /// + /// + /// An Authentication header is automatically added to the request. + /// The PoP token is bound to the HTTP request, more specifically to the HTTP method (GET, POST, etc.) and to the Uri (path and query, but not query parameters). + /// MSAL creates, reads and stores a key in memory that will be cycled every 8 hours. + /// This is an experimental API. The method signature may change in the future without involving a major version upgrade. + /// + /// + public T WithSignedHttpRequestProofOfPossession(PoPAuthenticationConfiguration popAuthenticationConfiguration) + { + CommonParameters.PopAuthenticationConfiguration = popAuthenticationConfiguration ?? throw new ArgumentNullException(nameof(popAuthenticationConfiguration)); + + CommonParameters.AuthenticationOperation = new PopAuthenticationOperation(CommonParameters.PopAuthenticationConfiguration, ServiceBundle); + + return this as T; + } } } diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs index 4135347f05..8df85a8f86 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs @@ -74,6 +74,18 @@ public AcquireTokenForClientParameterBuilder WithSendX5C(bool withSendX5C) return this; } + /// + /// Specifies that the certificate provided will be used for PoP tokens with MTLS (Mutual TLS) authentication. + /// For more information, refer to the documentation at: /// https://aka.ms/mstls-pop + /// + /// The current instance of to enable method chaining. + public AcquireTokenForClientParameterBuilder WithMtlsProofOfPossession() + { + ValidateUseOfExperimentalFeature(); + Parameters.UseMtlsPop = true; + return this; // Return the builder to allow method chaining + } + /// /// Please use WithAzureRegion on the ConfidentialClientApplicationBuilder object /// diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs index 6ae2e3858d..56ce5bd87b 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/Executors/ConfidentialClientExecutor.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client.ApiConfig.Parameters; +using Microsoft.Identity.Client.AuthScheme.PoP; using Microsoft.Identity.Client.Instance.Discovery; using Microsoft.Identity.Client.Internal; using Microsoft.Identity.Client.Internal.Requests; @@ -63,6 +64,16 @@ public async Task ExecuteAsync( requestParams.SendX5C = clientParameters.SendX5C ?? false; + requestParams.UseMtlsPop = clientParameters.UseMtlsPop; + + if (requestParams.UseMtlsPop) + { + commonParameters.MtlsCertificate = _confidentialClientApplication.Certificate; + commonParameters.AuthenticationOperation = new MtlsPopAuthenticationOperation(_confidentialClientApplication.Certificate); + ServiceBundle.Config.ClientCredential = null; + ServiceBundle.Config.UseMtlsPop = true; + } + var handler = new ClientCredentialRequest( ServiceBundle, requestParams, diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenForClientParameters.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenForClientParameters.cs index be7cfa4b21..2636f79b52 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenForClientParameters.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AcquireTokenForClientParameters.cs @@ -12,6 +12,11 @@ internal class AcquireTokenForClientParameters : AbstractAcquireTokenConfidentia /// public bool ForceRefresh { get; set; } + /// + /// Whether to use MTLS Proof of Possession (PoP) + /// + public bool UseMtlsPop { get; set; } = false; + /// public void LogParameters(ILoggerAdapter logger) { @@ -20,6 +25,7 @@ public void LogParameters(ILoggerAdapter logger) var builder = new StringBuilder(); builder.AppendLine("=== AcquireTokenForClientParameters ==="); builder.AppendLine("SendX5C: " + SendX5C); + builder.AppendLine("UseMtlsPop: " + UseMtlsPop); builder.AppendLine("ForceRefresh: " + ForceRefresh); logger.Info(builder.ToString()); } diff --git a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs index 95a181a4e4..ef0796fd0f 100644 --- a/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs +++ b/src/client/Microsoft.Identity.Client/AppConfig/ApplicationConfiguration.cs @@ -123,8 +123,9 @@ public string ClientVersion public bool IsPublicClient => !IsConfidentialClient && !IsManagedIdentity; public Func> AppTokenProvider; + public bool UseMtlsPop { get; internal set; } = false; -#region ClientCredentials + #region ClientCredentials public IClientCredential ClientCredential { get; internal set; } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs new file mode 100644 index 0000000000..50ee218221 --- /dev/null +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Microsoft.Identity.Client.AppConfig; +using Microsoft.Identity.Client.Cache.Items; +using Microsoft.Identity.Client.Internal; +using Microsoft.Identity.Client.OAuth2; +using Microsoft.Identity.Client.Utils; +#if SUPPORTS_SYSTEM_TEXT_JSON +using JObject = System.Text.Json.Nodes.JsonObject; +using JToken = System.Text.Json.Nodes.JsonNode; +#else +using Microsoft.Identity.Json; +using Microsoft.Identity.Json.Linq; +#endif + +namespace Microsoft.Identity.Client.AuthScheme.PoP +{ + internal class MtlsPopAuthenticationOperation : IAuthenticationOperation + { + private readonly X509Certificate2 _mtlsCert; + + public MtlsPopAuthenticationOperation(X509Certificate2 mtlsCert) + { + _mtlsCert = mtlsCert; + KeyId = mtlsCert.Thumbprint; + } + + public int TelemetryTokenType => (int)TokenType.Mtls; + + public string AuthorizationHeaderPrefix => Constants.MtlsPoPAuthHeaderPrefix; + + public string AccessTokenType => Constants.MtlsPoPTokenType; + + /// + /// For MTLS PoP, we use x5t + /// + public string KeyId { get; } + + public IReadOnlyDictionary GetTokenRequestParams() + { + return CollectionHelpers.GetEmptyDictionary(); + } + + public void FormatResult(AuthenticationResult authenticationResult) + { + var header = new JObject(); + header[JsonWebTokenConstants.KeyId] = KeyId; + header[JsonWebTokenConstants.Type] = Constants.MtlsPoPTokenType; + + authenticationResult.MtlsCertificate = _mtlsCert; + } + } +} diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/TokenType.cs b/src/client/Microsoft.Identity.Client/AuthScheme/TokenType.cs index 5f533c5beb..a81d213be7 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/TokenType.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/TokenType.cs @@ -32,6 +32,12 @@ internal enum TokenType /// Extension token type. /// Extension = 5 /// - Extension = 5 + Extension = 5, + + /// + /// MTLS token type. + /// MTLS = 5 + /// + Mtls = 5 } } diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs index 6130a80565..a9d8e1d911 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Globalization; using System.Security.Claims; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.Identity.Client.AuthScheme; using Microsoft.Identity.Client.Cache; @@ -42,6 +43,7 @@ public partial class AuthenticationResult /// Claims from the ID token /// Auth Code returned by the Microsoft identity platform when you use AcquireTokenByAuthorizationCode.WithSpaAuthorizationCode(). This auth code is meant to be redeemed by the frontend code. See https://aka.ms/msal-net/spa-auth-code /// Other properties from the token response. + /// MTLS certificate used in the token acquisition, if any. For MTLS Pop you may need to use this while calling the target resource public AuthenticationResult( // for backwards compat with 4.16- string accessToken, bool isExtendedLifeTimeToken, @@ -57,7 +59,8 @@ public partial class AuthenticationResult AuthenticationResultMetadata authenticationResultMetadata = null, ClaimsPrincipal claimsPrincipal = null, string spaAuthCode = null, - IReadOnlyDictionary additionalResponseParameters = null) + IReadOnlyDictionary additionalResponseParameters = null, + X509Certificate2 mtlsCertificate = null) { AccessToken = accessToken; #pragma warning disable CS0618 // Type or member is obsolete @@ -76,6 +79,7 @@ public partial class AuthenticationResult ClaimsPrincipal = claimsPrincipal; SpaAuthCode = spaAuthCode; AdditionalResponseParameters = additionalResponseParameters; + MtlsCertificate = mtlsCertificate; } /// @@ -315,6 +319,11 @@ internal AuthenticationResult() { } /// public AuthenticationResultMetadata AuthenticationResultMetadata { get; } + /// + /// Exposes the MTLS certificate used for authentication, if applicable. + /// /// Creates the content for an HTTP authorization header from this authentication result, so /// that you can call a protected API diff --git a/src/client/Microsoft.Identity.Client/Instance/Discovery/RegionDiscoveryProvider.cs b/src/client/Microsoft.Identity.Client/Instance/Discovery/RegionDiscoveryProvider.cs index b77a52d060..f767eafadb 100644 --- a/src/client/Microsoft.Identity.Client/Instance/Discovery/RegionDiscoveryProvider.cs +++ b/src/client/Microsoft.Identity.Client/Instance/Discovery/RegionDiscoveryProvider.cs @@ -14,6 +14,7 @@ internal class RegionDiscoveryProvider : IRegionDiscoveryProvider { private readonly IRegionManager _regionManager; public const string PublicEnvForRegional = "login.microsoft.com"; + public const string PublicEnvForMtls = "mtlsauth.microsoft.com"; public RegionDiscoveryProvider(IHttpManager httpManager, bool clearCache) { @@ -56,9 +57,14 @@ private static InstanceDiscoveryMetadataEntry CreateEntry(string originalEnv, st private static string GetRegionalizedEnvironment(Uri authority, string region, RequestContext requestContext) { - string host = authority.Host; + if (requestContext.ServiceBundle.Config.UseMtlsPop) + { + requestContext.Logger.Info(() => $"[Region discovery] Using MTLS public endpoint: {PublicEnvForMtls}"); + return $"{region}.{PublicEnvForMtls}"; + } + if (KnownMetadataProvider.IsPublicEnvironment(host)) { requestContext.Logger.Info(() => $"[Region discovery] Regionalized Environment is : {region}.{PublicEnvForRegional}. "); diff --git a/src/client/Microsoft.Identity.Client/Internal/Constants.cs b/src/client/Microsoft.Identity.Client/Internal/Constants.cs index 567e4261d3..2a4caa4baa 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Constants.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Constants.cs @@ -34,7 +34,9 @@ internal static class Constants public static readonly TimeSpan AccessTokenExpirationBuffer = TimeSpan.FromMinutes(5); public const string EnableSpaAuthCode = "1"; public const string PoPTokenType = "pop"; - public const string PoPAuthHeaderPrefix = "PoP"; + public const string MtlsPoPTokenType = "mtls_pop"; + public const string PoPAuthHeaderPrefix = "PoP"; + public const string MtlsPoPAuthHeaderPrefix = "mtls_pop"; public const string RequestConfirmation = "req_cnf"; public const string BearerAuthHeaderPrefix = "Bearer"; diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs index 5589800eee..d976728df7 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/AuthenticationRequestParameters.cs @@ -85,7 +85,7 @@ public AuthenticationRequestParameters( public AuthorityInfo AuthorityInfo => AuthorityManager.Authority.AuthorityInfo; - public AuthorityInfo AuthorityOverride => _commonParameters.AuthorityOverride; + public AuthorityInfo AuthorityOverride => _commonParameters.AuthorityOverride; #endregion @@ -163,6 +163,12 @@ public string LoginHint public string LongRunningOboCacheKey { get; set; } public KeyValuePair? CcsRoutingHint { get; set; } + + /// + /// Indicates if MTLS Proof of Possession should be used. + /// + public bool UseMtlsPop { get; set; } = false; + #endregion public void LogParameters() @@ -185,6 +191,7 @@ public void LogParameters() builder.AppendLine("ApiId - " + ApiId); builder.AppendLine("IsConfidentialClient - " + AppConfig.IsConfidentialClient); builder.AppendLine("SendX5C - " + SendX5C); + builder.AppendLine("UseMtlsPop - " + UseMtlsPop); builder.AppendLine("LoginHint - " + LoginHint); builder.AppendLine("IsBrokerConfigured - " + AppConfig.IsBrokerEnabled); builder.AppendLine("HomeAccountId - " + HomeAccountId); @@ -205,6 +212,7 @@ public void LogParameters() builder.AppendLine("ApiId - " + ApiId); builder.AppendLine("IsConfidentialClient - " + AppConfig.IsConfidentialClient); builder.AppendLine("SendX5C - " + SendX5C); + builder.AppendLine("UseMtlsPop - " + UseMtlsPop); builder.AppendLine("LoginHint ? " + !string.IsNullOrEmpty(LoginHint)); builder.AppendLine("IsBrokerConfigured - " + AppConfig.IsBrokerEnabled); builder.AppendLine("HomeAccountId - " + !string.IsNullOrEmpty(HomeAccountId)); diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs index 344cbdd017..000093d3a9 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs @@ -112,7 +112,7 @@ private async Task GetAccessTokenAsync( // Get a token from AAD if (ServiceBundle.Config.AppTokenProvider == null) { - MsalTokenResponse msalTokenResponse = await SendTokenRequestAsync(GetBodyParameters(), cancellationToken).ConfigureAwait(false); + MsalTokenResponse msalTokenResponse = await SendTokenRequestAsync(GetBodyParameters(_clientParameters.UseMtlsPop), cancellationToken).ConfigureAwait(false); return await CacheTokenResponseAndCreateAuthenticationResultAsync(msalTokenResponse).ConfigureAwait(false); } @@ -224,13 +224,20 @@ protected override SortedSet GetOverriddenScopes(ISet inputScope return new SortedSet(inputScopes); } - private Dictionary GetBodyParameters() + private Dictionary GetBodyParameters(bool useMtlsPop) { var dict = new Dictionary { [OAuth2Parameter.GrantType] = OAuth2GrantType.ClientCredentials, [OAuth2Parameter.Scope] = AuthenticationRequestParameters.Scope.AsSingleString() }; + + // Only add TokenType if useMtlsPop is true + if (useMtlsPop) + { + dict[OAuth2Parameter.TokenType] = RequestTokenType.MTLSPop; + } + return dict; } diff --git a/src/client/Microsoft.Identity.Client/OAuth2/OAuthConstants.cs b/src/client/Microsoft.Identity.Client/OAuth2/OAuthConstants.cs index 21359ba9a9..37c289fe91 100644 --- a/src/client/Microsoft.Identity.Client/OAuth2/OAuthConstants.cs +++ b/src/client/Microsoft.Identity.Client/OAuth2/OAuthConstants.cs @@ -90,6 +90,12 @@ internal static class OAuth2Error public const string AuthorizationPending = "authorization_pending"; } + internal static class RequestTokenType + { + public const string Bearer = "bearer"; + public const string MTLSPop = "mtls_pop"; + } + internal static class OAuth2Value { public const string CodeChallengeMethodValue = "S256"; diff --git a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.NetFwk.cs b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.NetFwk.cs index 1f3b82857a..3fc2b31cf0 100644 --- a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.NetFwk.cs +++ b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.NetFwk.cs @@ -51,8 +51,8 @@ public void TestInitialize() } [DataTestMethod] - [DataRow(Cloud.Public, TargetFrameworks.NetFx | TargetFrameworks.NetCore )] - [DataRow(Cloud.Adfs, TargetFrameworks.NetFx | TargetFrameworks.NetCore )] + [DataRow(Cloud.Public, TargetFrameworks.NetFx | TargetFrameworks.NetCore)] + [DataRow(Cloud.Adfs, TargetFrameworks.NetFx | TargetFrameworks.NetCore)] //[DataRow(Cloud.PPE, TargetFrameworks.NetFx)] [DataRow(Cloud.Public, TargetFrameworks.NetCore, true)] //[DataRow(Cloud.Arlington)] - cert not setup @@ -63,7 +63,7 @@ public async Task WithCertificate_TestAsync(Cloud cloud, TargetFrameworks runOn, } [DataTestMethod] - [DataRow(Cloud.Public, TargetFrameworks.NetCore)] + [DataRow(Cloud.Public, TargetFrameworks.NetCore)] [DataRow(Cloud.Adfs, TargetFrameworks.NetFx)] //[DataRow(Cloud.Arlington, TargetFrameworks.NetCore)] TODO: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/4905 //[DataRow(Cloud.PPE)] - secret not setup @@ -74,8 +74,8 @@ public async Task WithSecret_TestAsync(Cloud cloud, TargetFrameworks runOn) } [DataTestMethod] - [DataRow(Cloud.Public, TargetFrameworks.NetCore)] - [DataRow(Cloud.Adfs, TargetFrameworks.NetCore)] + [DataRow(Cloud.Public, TargetFrameworks.NetCore)] + [DataRow(Cloud.Adfs, TargetFrameworks.NetCore)] //[DataRow(Cloud.PPE, TargetFrameworks.NetCore)] // [DataRow(Cloud.Arlington)] - cert not setup public async Task WithClientAssertion_Manual_TestAsync(Cloud cloud, TargetFrameworks runOn) @@ -85,7 +85,7 @@ public async Task WithClientAssertion_Manual_TestAsync(Cloud cloud, TargetFramew } [DataTestMethod] - [DataRow(Cloud.Public, TargetFrameworks.NetFx )] + [DataRow(Cloud.Public, TargetFrameworks.NetFx)] [DataRow(Cloud.Adfs, TargetFrameworks.NetFx)] //[DataRow(Cloud.PPE, TargetFrameworks.NetCore)] // [DataRow(Cloud.Arlington)] - cert not setup @@ -134,7 +134,7 @@ public async Task WithClientClaims_SendX5C_OverrideClaims_TestAsync(Cloud cloud, } [DataTestMethod] - [DataRow(Cloud.Public, TargetFrameworks.NetCore)] + [DataRow(Cloud.Public, TargetFrameworks.NetCore)] public async Task WithOnBeforeTokenRequest_TestAsync(Cloud cloud, TargetFrameworks runOn) { runOn.AssertFramework(); @@ -148,7 +148,7 @@ public async Task WithOnBeforeTokenRequest_TestAsync(Cloud cloud, TargetFramewor .WithAuthority(settings.Authority, true) .WithTestLogging() .Build(); - + authResult = await confidentialApp .AcquireTokenForClient(settings.AppScopes) .OnBeforeTokenRequest((data) => @@ -171,7 +171,7 @@ public async Task WithOnBeforeTokenRequest_TestAsync(Cloud cloud, TargetFramewor .ExecuteAsync() .ConfigureAwait(false); - Assert.AreEqual(TokenSource.Cache, authResult.AuthenticationResultMetadata.TokenSource); + Assert.AreEqual(TokenSource.Cache, authResult.AuthenticationResultMetadata.TokenSource); } [RunOn(TargetFrameworks.NetCore)] @@ -229,9 +229,9 @@ private static void ModifyRequest(OnBeforeTokenRequestData data, X509Certificate string tokenEndpoint = data.RequestUri.AbsoluteUri; string assertion = GetSignedClientAssertionManual( - issuer: clientId, - audience: tokenEndpoint, - certificate: certificate, + issuer: clientId, + audience: tokenEndpoint, + certificate: certificate, useSha2AndPss: true); data.BodyParameters.Add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); @@ -294,9 +294,9 @@ private async Task RunClientCredsAsync(Cloud cloud, CredentialType credentialTyp } private static IConfidentialClientApplication CreateApp( - CredentialType credentialType, - IConfidentialAppSettings settings, - bool sendX5C, + CredentialType credentialType, + IConfidentialAppSettings settings, + bool sendX5C, bool useSha2AndPssForAssertion) { var builder = ConfidentialClientApplicationBuilder @@ -420,9 +420,9 @@ private static string GetSignedClientAssertionUsingWilson( /// /// private static string GetSignedClientAssertionManual( - string issuer, - string audience, - X509Certificate2 certificate, + string issuer, + string audience, + X509Certificate2 certificate, bool useSha2AndPss) { const uint JwtToAadLifetimeInSeconds = 60 * 10; // Ten minutes @@ -464,7 +464,7 @@ private static string GetSignedClientAssertionManual( { "x5t", Base64UrlHelpers.Encode(certificate.GetCertHash())}, }; } - + var headerBytes = JsonSerializer.SerializeToUtf8Bytes(header); var claimsBytes = JsonSerializer.SerializeToUtf8Bytes(claims); diff --git a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/PoPTests.NetFwk.cs b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/PoPTests.NetFwk.cs index b60c70f865..6f49f3ded2 100644 --- a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/PoPTests.NetFwk.cs +++ b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/PoPTests.NetFwk.cs @@ -78,14 +78,13 @@ public async Task HappyPath_Async() var confidentialApp = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .WithTestLogging() .Build(); var result = await confidentialApp.AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -107,7 +106,6 @@ private async Task BearerAndPoP_CanCoexist_Async() var cca = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithClientSecret(settings.GetSecret()) .WithTestLogging() .WithAuthority(settings.Authority).Build(); @@ -117,7 +115,7 @@ private async Task BearerAndPoP_CanCoexist_Async() Trace.WriteLine("Getting a PoP token"); AuthenticationResult result = await cca .AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync() .ConfigureAwait(false); @@ -155,7 +153,6 @@ private async Task MultipleKeys_Async() var settings = ConfidentialAppSettings.GetSettings(Cloud.Public); var cca = ConfidentialClientApplicationBuilder.Create(settings.ClientId) - .WithExperimentalFeatures() .WithTestLogging() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()).Build(); @@ -163,7 +160,7 @@ private async Task MultipleKeys_Async() var result = await cca .AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig1) + .WithSignedHttpRequestProofOfPossession(popConfig1) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -177,7 +174,6 @@ private async Task MultipleKeys_Async() // recreate the pca to ensure that the silent call is served from the cache, i.e. the key remains stable cca = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .WithHttpClientFactory(new NoAccessHttpClientFactory()) // token should be served from the cache, no network access necessary @@ -186,7 +182,7 @@ private async Task MultipleKeys_Async() result = await cca .AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig1) + .WithSignedHttpRequestProofOfPossession(popConfig1) .ExecuteAsync() .ConfigureAwait(false); @@ -202,7 +198,7 @@ private async Task MultipleKeys_Async() // Call some other Uri - the same pop assertion can be reused, i.e. no need to call Evo result = await cca .AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig2) + .WithSignedHttpRequestProofOfPossession(popConfig2) .ExecuteAsync() .ConfigureAwait(false); @@ -223,7 +219,6 @@ public async Task PopTestWithConfigObjectAsync() var confidentialApp = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .WithTestLogging() @@ -234,7 +229,7 @@ public async Task PopTestWithConfigObjectAsync() popConfig.HttpMethod = HttpMethod.Get; var result = await confidentialApp.AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -260,7 +255,6 @@ public async Task PopTestWithRSAAsync() var confidentialApp = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .Build(); @@ -271,7 +265,7 @@ public async Task PopTestWithRSAAsync() popConfig.HttpMethod = HttpMethod.Get; var result = await confidentialApp.AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -298,7 +292,6 @@ public async Task ROPC_PopTestWithRSAAsync() var confidentialApp = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .Build(); @@ -309,7 +302,7 @@ public async Task ROPC_PopTestWithRSAAsync() popConfig.HttpMethod = HttpMethod.Get; var result = await (confidentialApp as IByUsernameAndPassword).AcquireTokenByUsernamePassword(s_ropcScope, labResponse.User.Upn, labResponse.User.GetOrFetchPassword()) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -329,7 +322,6 @@ public async Task PopTest_ExternalWilsonSigning_Async() var confidentialApp = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .Build(); @@ -345,7 +337,7 @@ public async Task PopTest_ExternalWilsonSigning_Async() }; var result = await confidentialApp.AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -380,7 +372,7 @@ public async Task PopTest_ExternalWilsonSigning_Async() req, "pop"); var result2 = await confidentialApp.AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); Assert.AreEqual( @@ -395,7 +387,6 @@ public async Task PopTestWithECDAsync() var confidentialApp = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .Build(); @@ -406,7 +397,7 @@ public async Task PopTestWithECDAsync() popConfig.HttpMethod = HttpMethod.Post; var result = await confidentialApp.AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -527,7 +518,6 @@ public async Task InMemoryCryptoProvider_AlgIsPS256() var confidentialApp = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .Build(); @@ -553,7 +543,7 @@ public async Task InMemoryCryptoProvider_AlgIsPS256() }; var result = await confidentialApp.AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -580,7 +570,6 @@ public async Task InMemoryCryptoProvider_WithGraph() var confidentialApp = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .Build(); @@ -606,7 +595,7 @@ public async Task InMemoryCryptoProvider_WithGraph() }; var result = await confidentialApp.AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -643,7 +632,7 @@ public async Task InMemoryCryptoProvider_WithGraph() }; var resultWithNonce = await confidentialApp.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" }) - .WithProofOfPossession(popConfigWithNonce) + .WithSignedHttpRequestProofOfPossession(popConfigWithNonce) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -668,7 +657,6 @@ public async Task PoPToken_ShouldHaveCorrectAlgorithm_PS256_Async() var settings = ConfidentialAppSettings.GetSettings(Cloud.Public); var confidentialApp = ConfidentialClientApplicationBuilder .Create(settings.ClientId) - .WithExperimentalFeatures() .WithAuthority(settings.Authority) .WithClientSecret(settings.GetSecret()) .Build(); @@ -681,7 +669,7 @@ public async Task PoPToken_ShouldHaveCorrectAlgorithm_PS256_Async() // Act var result = await confidentialApp.AcquireTokenForClient(s_keyvaultScope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); diff --git a/tests/Microsoft.Identity.Test.Integration.netcore/Infrastructure/MsalExtensions.cs b/tests/Microsoft.Identity.Test.Integration.netcore/Infrastructure/MsalExtensions.cs index 07af7483f2..839fc14520 100644 --- a/tests/Microsoft.Identity.Test.Integration.netcore/Infrastructure/MsalExtensions.cs +++ b/tests/Microsoft.Identity.Test.Integration.netcore/Infrastructure/MsalExtensions.cs @@ -37,7 +37,7 @@ var popAuthenticationConfiguration PopCryptoProvider = new SigningCredentialsToPopCryptoProviderAdapter(popCredentials, assertNotSigned: true) }; - return builder.WithProofOfPossession(popAuthenticationConfiguration); + return builder.WithSignedHttpRequestProofOfPossession(popAuthenticationConfiguration); } } diff --git a/tests/Microsoft.Identity.Test.Unit/ExceptionTests/ExperimentalFeatureTests.cs b/tests/Microsoft.Identity.Test.Unit/ExceptionTests/ExperimentalFeatureTests.cs index cedeffe2f1..5ad4b1a385 100644 --- a/tests/Microsoft.Identity.Test.Unit/ExceptionTests/ExperimentalFeatureTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ExceptionTests/ExperimentalFeatureTests.cs @@ -17,18 +17,21 @@ namespace Microsoft.Identity.Test.Unit.ExceptionTests [TestClass] public class ExperimentalFeatureTests { + private static readonly string[] s_scopes = ["scope"]; #if NETFRAMEWORK [TestMethod] public async Task ExperimentalFeatureExceptionAsync() { - var cca = ConfidentialClientApplicationBuilder.Create(Guid.NewGuid().ToString()).WithClientSecret("some-secret").Build(); + IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder + .Create(Guid.NewGuid().ToString()) + .WithCertificate(CertHelper.GetOrCreateTestCert()).Build(); + MsalClientException ex = await AssertException.TaskThrowsAsync( - () => cca.AcquireTokenForClient(new[] { "scope" }).WithProofOfPossession(null).ExecuteAsync()) + () => cca.AcquireTokenForClient(s_scopes).WithMtlsProofOfPossession().ExecuteAsync()) .ConfigureAwait(false); Assert.AreEqual(MsalError.ExperimentalFeature, ex.ErrorCode); } #endif - } } diff --git a/tests/Microsoft.Identity.Test.Unit/pop/PoPTests.cs b/tests/Microsoft.Identity.Test.Unit/pop/PoPTests.cs index 311600a8cf..c4cbc9b8c4 100644 --- a/tests/Microsoft.Identity.Test.Unit/pop/PoPTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/pop/PoPTests.cs @@ -59,7 +59,6 @@ public async Task POP_ShrValidation_Async() ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) .WithClientSecret(TestConstants.ClientSecret) .WithHttpManager(httpManager) - .WithExperimentalFeatures(true) .BuildConcrete(); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(ProtectedUrl)); @@ -72,7 +71,7 @@ public async Task POP_ShrValidation_Async() // Act var result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray()) .WithTenantId(TestConstants.Utid) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync() .ConfigureAwait(false); @@ -93,7 +92,6 @@ public async Task POP_NoHttpRequest_Async() ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) .WithClientSecret(TestConstants.ClientSecret) .WithHttpManager(httpManager) - .WithExperimentalFeatures(true) .BuildConcrete(); // no HTTP method binding, but custom nonce @@ -106,7 +104,7 @@ public async Task POP_NoHttpRequest_Async() // Act var result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray()) .WithTenantId(TestConstants.Utid) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync() .ConfigureAwait(false); @@ -131,7 +129,6 @@ public async Task POP_WithCustomNonce_Async() ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) .WithClientSecret(TestConstants.ClientSecret) .WithHttpManager(httpManager) - .WithExperimentalFeatures(true) .BuildConcrete(); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(ProtectedUrl)); @@ -143,7 +140,7 @@ public async Task POP_WithCustomNonce_Async() var result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray()) .WithTenantId(TestConstants.Utid) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync() .ConfigureAwait(false); @@ -407,7 +404,6 @@ public async Task CacheKey_Includes_POPKid_Async() ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) .WithClientSecret(TestConstants.ClientSecret) .WithHttpManager(httpManager) - .WithExperimentalFeatures(true) .BuildConcrete(); var testTimeService = new TestTimeService(); PoPCryptoProviderFactory.TimeService = testTimeService; @@ -423,7 +419,7 @@ public async Task CacheKey_Includes_POPKid_Async() Trace.WriteLine("1. AcquireTokenForClient "); var result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray()) .WithTenantId(TestConstants.Utid) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync() .ConfigureAwait(false); @@ -450,7 +446,7 @@ public async Task CacheKey_Includes_POPKid_Async() Trace.WriteLine("1. AcquireTokenForClient again, after time passes - expect POP key rotation"); result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray()) .WithTenantId(TestConstants.Utid) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync() .ConfigureAwait(false); @@ -584,7 +580,6 @@ public async Task POP_SignatureValidationWithPS256_Async() ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) .WithClientSecret(TestConstants.ClientSecret) .WithHttpManager(httpManager) - .WithExperimentalFeatures(true) .BuildConcrete(); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(ProtectedUrl)); @@ -600,7 +595,7 @@ public async Task POP_SignatureValidationWithPS256_Async() // Act var result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray()) .WithTenantId(TestConstants.Utid) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync() .ConfigureAwait(false); @@ -653,7 +648,6 @@ public async Task TokenGenerationAndValidation_Async() ConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) .WithClientSecret(TestConstants.ClientSecret) .WithHttpManager(httpManager) - .WithExperimentalFeatures(true) .BuildConcrete(); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, new Uri(ProtectedUrl)); @@ -666,7 +660,7 @@ public async Task TokenGenerationAndValidation_Async() // Act var result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray()) .WithTenantId(TestConstants.Utid) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync() .ConfigureAwait(false); diff --git a/tests/Microsoft.Identity.Test.Unit/pop/PopAuthenticationOperationTests.cs b/tests/Microsoft.Identity.Test.Unit/pop/PopAuthenticationOperationTests.cs index 85620be965..3a511aa5c6 100644 --- a/tests/Microsoft.Identity.Test.Unit/pop/PopAuthenticationOperationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/pop/PopAuthenticationOperationTests.cs @@ -113,7 +113,6 @@ public async Task ValidateKeyExpirationAsync() var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) .WithHttpManager(harness.HttpManager) - .WithExperimentalFeatures() .WithClientSecret("some-secret") .BuildConcrete(); @@ -132,7 +131,7 @@ public async Task ValidateKeyExpirationAsync() PoPCryptoProviderFactory.TimeService = testClock; var result = await app.AcquireTokenForClient(TestConstants.s_scope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); var initialToken = result.AccessToken; @@ -142,7 +141,7 @@ public async Task ValidateKeyExpirationAsync() PoPCryptoProviderFactory.TimeService = testClock; result = await app.AcquireTokenForClient(TestConstants.s_scope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -155,7 +154,7 @@ public async Task ValidateKeyExpirationAsync() PoPCryptoProviderFactory.TimeService = testClock; result = await app.AcquireTokenForClient(TestConstants.s_scope) - .WithProofOfPossession(popConfig) + .WithSignedHttpRequestProofOfPossession(popConfig) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false);