From 97c5d95f1c2a557782964a76f08471b272918b7e Mon Sep 17 00:00:00 2001 From: Gladwin Johnson Date: Mon, 21 Oct 2024 17:30:00 -0700 Subject: [PATCH 1/2] very very draft --- .../AcquireTokenForClientParameterBuilder.cs | 10 +++ .../Executors/ConfidentialClientExecutor.cs | 11 +++ ...cquireTokenConfidentialClientParameters.cs | 5 ++ .../AppConfig/ApplicationConfiguration.cs | 3 +- .../PoP/MtlsPopAuthenticationOperation.cs | 78 +++++++++++++++++ .../Discovery/RegionDiscoveryProvider.cs | 8 +- .../Internal/Constants.cs | 4 +- .../AuthenticationRequestParameters.cs | 10 ++- .../Requests/ClientCredentialRequest.cs | 11 ++- .../OAuth2/OAuthConstants.cs | 6 ++ .../ClientCredentialsTests.NetFwk.cs | 87 +++++++++++++------ 11 files changed, 199 insertions(+), 34 deletions(-) create mode 100644 src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs index 4135347f05..6fc2f7e254 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs @@ -74,6 +74,16 @@ public AcquireTokenForClientParameterBuilder WithSendX5C(bool withSendX5C) return this; } + /// + /// Specifies that the certificate provided will be used for PoP tokens with MTLS (Mutual TLS) authentication. + /// + /// The current instance of to enable method chaining. + public AcquireTokenForClientParameterBuilder WithMtlsPop() + { + 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/AbstractAcquireTokenConfidentialClientParameters.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs index 6237f45345..238bf879b1 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs @@ -20,5 +20,10 @@ internal abstract class AbstractAcquireTokenConfidentialClientParameters /// This overrides application config settings. /// public bool? SendX5C { get; set; } + + /// + /// Whether to use MTLS Proof of Possession (PoP) + /// + public bool UseMtlsPop { get; set; } = false; } } 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..17e807c8be --- /dev/null +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs @@ -0,0 +1,78 @@ +// 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.Pop; + + public string AuthorizationHeaderPrefix => Constants.MtlsPoPAuthHeaderPrefix; + + public string AccessTokenType => Constants.MtlsPoPTokenType; + + /// + /// For PoP, we chose to use the base64(jwk_thumbprint) + /// + 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.Certificate = _mtlsCert; + } + + private JObject CreateBody(string accessToken) + { + return null; + } + + /// + /// A key ID that uniquely describes a public / private key pair. While KeyID is not normally + /// strict, AAD support for PoP requires that we use the base64 encoded JWK thumbprint, as described by + /// https://tools.ietf.org/html/rfc7638 + /// + private static byte[] ComputeThumbprint(string canonicalJwk) + { + using (SHA256 hash = SHA256.Create()) + { + return hash.ComputeHash(Encoding.UTF8.GetBytes(canonicalJwk)); + } + } + } +} 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..3cb5b60fea 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,15 @@ public async Task WithCertificate_TestAsync(Cloud cloud, TargetFrameworks runOn, } [DataTestMethod] - [DataRow(Cloud.Public, TargetFrameworks.NetCore)] + [DataRow(Cloud.Public, TargetFrameworks.NetFx | TargetFrameworks.NetCore)] + public async Task WithMtlsCertificate_TestAsync(Cloud cloud, TargetFrameworks runOn, bool useAppIdUri = false) + { + runOn.AssertFramework(); + await RunClientCredsAsync(cloud, CredentialType.Cert, useAppIdUri, false, true).ConfigureAwait(false); + } + + [DataTestMethod] + [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 +82,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 +93,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 +142,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 +156,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 +179,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,16 +237,20 @@ 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"); data.BodyParameters.Add("client_assertion", assertion); } - private async Task RunClientCredsAsync(Cloud cloud, CredentialType credentialType, bool UseAppIdUri = false, bool sendX5C = false) + private async Task RunClientCredsAsync(Cloud cloud, + CredentialType credentialType, + bool UseAppIdUri = false, + bool sendX5C = false, + bool useMtls = false) { Trace.WriteLine($"Running test with settings for cloud {cloud}, credential type {credentialType}"); IConfidentialAppSettings settings = ConfidentialAppSettings.GetSettings(cloud); @@ -247,12 +259,20 @@ private async Task RunClientCredsAsync(Cloud cloud, CredentialType credentialTyp AuthenticationResult authResult; - IConfidentialClientApplication confidentialApp = CreateApp(credentialType, settings, sendX5C, cloud != Cloud.Adfs); + IConfidentialClientApplication confidentialApp = CreateApp(credentialType, settings, sendX5C, cloud != Cloud.Adfs, useMtls); var appCacheRecorder = confidentialApp.AppTokenCache.RecordAccess(); Guid correlationId = Guid.NewGuid(); - authResult = await confidentialApp + + AcquireTokenForClientParameterBuilder acquireTokenBuilder = confidentialApp .AcquireTokenForClient(settings.AppScopes) - .WithCorrelationId(correlationId) + .WithCorrelationId(correlationId); + + if (useMtls) + { + acquireTokenBuilder = acquireTokenBuilder.WithMtlsPop(); + } + + authResult = await acquireTokenBuilder .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -294,15 +314,26 @@ private async Task RunClientCredsAsync(Cloud cloud, CredentialType credentialTyp } private static IConfidentialClientApplication CreateApp( - CredentialType credentialType, - IConfidentialAppSettings settings, - bool sendX5C, - bool useSha2AndPssForAssertion) + CredentialType credentialType, + IConfidentialAppSettings settings, + bool sendX5C, + bool useSha2AndPssForAssertion, + bool useMtls = false) { - var builder = ConfidentialClientApplicationBuilder - .Create(settings.ClientId) - .WithAuthority(settings.Authority, true) - .WithTestLogging(); + ConfidentialClientApplicationBuilder builder = ConfidentialClientApplicationBuilder + .Create(settings.ClientId).WithTestLogging(); + + //using an existing test for now to test the flow + //better to add unit tests for now + if (useMtls) + { + builder = builder.WithAzureRegion("centraluseuap") + .WithTenantId("72f988bf-86f1-41af-91ab-2d7cd011db47"); + } + else + { + builder = builder.WithAuthority(settings.Authority, true); + } switch (credentialType) { @@ -420,9 +451,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 +495,7 @@ private static string GetSignedClientAssertionManual( { "x5t", Base64UrlHelpers.Encode(certificate.GetCertHash())}, }; } - + var headerBytes = JsonSerializer.SerializeToUtf8Bytes(header); var claimsBytes = JsonSerializer.SerializeToUtf8Bytes(claims); From 88806793dc1a0805457670b9edf9ecf2eef6fa99 Mon Sep 17 00:00:00 2001 From: Gladwin Johnson Date: Sat, 26 Oct 2024 08:26:54 -0700 Subject: [PATCH 2/2] address comments --- ...ntialClientAcquireTokenParameterBuilder.cs | 25 ++++++++++ .../AcquireTokenForClientParameterBuilder.cs | 4 +- ...cquireTokenConfidentialClientParameters.cs | 5 -- .../AcquireTokenForClientParameters.cs | 6 +++ .../PoP/MtlsPopAuthenticationOperation.cs | 24 ++------- .../AuthScheme/TokenType.cs | 8 ++- .../AuthenticationResult.cs | 11 ++++- .../ClientCredentialsTests.NetFwk.cs | 49 ++++--------------- .../HeadlessTests/PoPTests.NetFwk.cs | 42 ++++++---------- .../Infrastructure/MsalExtensions.cs | 2 +- .../ExperimentalFeatureTests.cs | 9 ++-- .../pop/PoPTests.cs | 20 +++----- .../pop/PopAuthenticationOperationTests.cs | 7 ++- 13 files changed, 95 insertions(+), 117 deletions(-) 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 6fc2f7e254..8df85a8f86 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenForClientParameterBuilder.cs @@ -76,10 +76,12 @@ public AcquireTokenForClientParameterBuilder WithSendX5C(bool withSendX5C) /// /// 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 WithMtlsPop() + public AcquireTokenForClientParameterBuilder WithMtlsProofOfPossession() { + ValidateUseOfExperimentalFeature(); Parameters.UseMtlsPop = true; return this; // Return the builder to allow method chaining } diff --git a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs index 238bf879b1..6237f45345 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/Parameters/AbstractAcquireTokenConfidentialClientParameters.cs @@ -20,10 +20,5 @@ internal abstract class AbstractAcquireTokenConfidentialClientParameters /// This overrides application config settings. /// public bool? SendX5C { get; set; } - - /// - /// Whether to use MTLS Proof of Possession (PoP) - /// - public bool UseMtlsPop { get; set; } = false; } } 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/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs index 17e807c8be..50ee218221 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs @@ -32,14 +32,14 @@ public MtlsPopAuthenticationOperation(X509Certificate2 mtlsCert) KeyId = mtlsCert.Thumbprint; } - public int TelemetryTokenType => (int)TokenType.Pop; + public int TelemetryTokenType => (int)TokenType.Mtls; public string AuthorizationHeaderPrefix => Constants.MtlsPoPAuthHeaderPrefix; public string AccessTokenType => Constants.MtlsPoPTokenType; /// - /// For PoP, we chose to use the base64(jwk_thumbprint) + /// For MTLS PoP, we use x5t /// public string KeyId { get; } @@ -54,25 +54,7 @@ public void FormatResult(AuthenticationResult authenticationResult) header[JsonWebTokenConstants.KeyId] = KeyId; header[JsonWebTokenConstants.Type] = Constants.MtlsPoPTokenType; - //authenticationResult.Certificate = _mtlsCert; - } - - private JObject CreateBody(string accessToken) - { - return null; - } - - /// - /// A key ID that uniquely describes a public / private key pair. While KeyID is not normally - /// strict, AAD support for PoP requires that we use the base64 encoded JWK thumbprint, as described by - /// https://tools.ietf.org/html/rfc7638 - /// - private static byte[] ComputeThumbprint(string canonicalJwk) - { - using (SHA256 hash = SHA256.Create()) - { - return hash.ComputeHash(Encoding.UTF8.GetBytes(canonicalJwk)); - } + 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/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.NetFwk.cs b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.NetFwk.cs index 3cb5b60fea..3fc2b31cf0 100644 --- a/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.NetFwk.cs +++ b/tests/Microsoft.Identity.Test.Integration.netcore/HeadlessTests/ClientCredentialsTests.NetFwk.cs @@ -62,14 +62,6 @@ public async Task WithCertificate_TestAsync(Cloud cloud, TargetFrameworks runOn, await RunClientCredsAsync(cloud, CredentialType.Cert, useAppIdUri).ConfigureAwait(false); } - [DataTestMethod] - [DataRow(Cloud.Public, TargetFrameworks.NetFx | TargetFrameworks.NetCore)] - public async Task WithMtlsCertificate_TestAsync(Cloud cloud, TargetFrameworks runOn, bool useAppIdUri = false) - { - runOn.AssertFramework(); - await RunClientCredsAsync(cloud, CredentialType.Cert, useAppIdUri, false, true).ConfigureAwait(false); - } - [DataTestMethod] [DataRow(Cloud.Public, TargetFrameworks.NetCore)] [DataRow(Cloud.Adfs, TargetFrameworks.NetFx)] @@ -246,11 +238,7 @@ private static void ModifyRequest(OnBeforeTokenRequestData data, X509Certificate data.BodyParameters.Add("client_assertion", assertion); } - private async Task RunClientCredsAsync(Cloud cloud, - CredentialType credentialType, - bool UseAppIdUri = false, - bool sendX5C = false, - bool useMtls = false) + private async Task RunClientCredsAsync(Cloud cloud, CredentialType credentialType, bool UseAppIdUri = false, bool sendX5C = false) { Trace.WriteLine($"Running test with settings for cloud {cloud}, credential type {credentialType}"); IConfidentialAppSettings settings = ConfidentialAppSettings.GetSettings(cloud); @@ -259,20 +247,12 @@ private async Task RunClientCredsAsync(Cloud cloud, AuthenticationResult authResult; - IConfidentialClientApplication confidentialApp = CreateApp(credentialType, settings, sendX5C, cloud != Cloud.Adfs, useMtls); + IConfidentialClientApplication confidentialApp = CreateApp(credentialType, settings, sendX5C, cloud != Cloud.Adfs); var appCacheRecorder = confidentialApp.AppTokenCache.RecordAccess(); Guid correlationId = Guid.NewGuid(); - - AcquireTokenForClientParameterBuilder acquireTokenBuilder = confidentialApp + authResult = await confidentialApp .AcquireTokenForClient(settings.AppScopes) - .WithCorrelationId(correlationId); - - if (useMtls) - { - acquireTokenBuilder = acquireTokenBuilder.WithMtlsPop(); - } - - authResult = await acquireTokenBuilder + .WithCorrelationId(correlationId) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); @@ -317,23 +297,12 @@ private static IConfidentialClientApplication CreateApp( CredentialType credentialType, IConfidentialAppSettings settings, bool sendX5C, - bool useSha2AndPssForAssertion, - bool useMtls = false) + bool useSha2AndPssForAssertion) { - ConfidentialClientApplicationBuilder builder = ConfidentialClientApplicationBuilder - .Create(settings.ClientId).WithTestLogging(); - - //using an existing test for now to test the flow - //better to add unit tests for now - if (useMtls) - { - builder = builder.WithAzureRegion("centraluseuap") - .WithTenantId("72f988bf-86f1-41af-91ab-2d7cd011db47"); - } - else - { - builder = builder.WithAuthority(settings.Authority, true); - } + var builder = ConfidentialClientApplicationBuilder + .Create(settings.ClientId) + .WithAuthority(settings.Authority, true) + .WithTestLogging(); switch (credentialType) { 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);