Skip to content

Commit

Permalink
Implementing sendX5C on silent client credential call (#1169)
Browse files Browse the repository at this point in the history
* Implementing sendX5C on silent client credential call

* Refactoring.
adding additional test validation.

* Breaking up super long string literal.
refactoring for PR comments.
  • Loading branch information
trwalke committed May 24, 2019
1 parent 1e97e6f commit 7bec7a4
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public AcquireTokenForClientParameterBuilder WithForceRefresh(bool forceRefresh)

/// <summary>
/// Specifies if the x5c claim (public key of the certificate) should be sent to the STS.
/// Sending the x5x enables application developers to achieve easy certificate roll-over in Azure AD:
/// Sending the x5c enables application developers to achieve easy certificate roll-over in Azure AD:
/// this method will send the public certificate to Azure AD along with the token request,
/// so that Azure AD can use it to validate the subject name based on a trusted issuer policy.
/// This saves the application admin from the need to explicitly manage the certificate rollover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,24 @@ internal override ApiEvent.ApiIds CalculateApiEventId()

internal override ApiTelemetryId ApiTelemetryId => ApiTelemetryId.AcquireTokenSilent;

/// <summary>
/// Specifies if the x5c claim (public key of the certificate) should be sent to the STS.
/// Sending the x5c enables application developers to achieve easy certificate roll-over in Azure AD:
/// this method will send the public certificate to Azure AD along with the token request,
/// so that Azure AD can use it to validate the subject name based on a trusted issuer policy.
/// This saves the application admin from the need to explicitly manage the certificate rollover
/// (either via portal or powershell/CLI operation)
/// </summary>
/// <param name="withSendX5C"><c>true</c> if the x5c should be sent. Otherwise <c>false</c>.
/// The default is <c>false</c></param>
/// <returns>The builder to chain the .With methods</returns>
public AcquireTokenSilentParameterBuilder WithSendX5C(bool withSendX5C)
{
CommonParameters.AddApiTelemetryFeature(ApiTelemetryFeature.WithSendX5C);
Parameters.SendX5C = withSendX5C;
return this;
}

/// <summary>
///
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public ClientApplicationBaseExecutor(IServiceBundle serviceBundle, ClientApplica
requestContext,
_clientApplicationBase.UserTokenCacheInternal);

requestParameters.SendX5C = silentParameters.SendX5C;

var handler = new SilentRequest(ServiceBundle, requestParameters, silentParameters);
return await handler.RunAsync(cancellationToken).ConfigureAwait(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal class AcquireTokenSilentParameters : IAcquireTokenParameters
public bool ForceRefresh { get; set; }
public string LoginHint { get; set; }
public IAccount Account { get; set; }
public bool SendX5C { get; set; }

/// <inheritdoc />
public void LogParameters(ICoreLogger logger)
Expand Down
23 changes: 22 additions & 1 deletion tests/Microsoft.Identity.Test.Common/MsalTestConstants.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Cache;
using Microsoft.Identity.Client.Internal;
Expand Down Expand Up @@ -71,7 +73,26 @@ internal static class MsalTestConstants
public const string LocalAccountId = "test_local_account_id";
public const string GivenName = "Joe";
public const string FamilyName = "Doe";
public const string Username = "joe@localhost.com";
public const string Username = "joe@localhost.com";

//This value is only for testing purposes. It is for a certificate that is not used for anything other than running tests
public const string _defaultx5cValue = @"MIIDHzCCAgegAwIBAgIQM6NFYNBJ9rdOiK+C91ZzFDANBgkqhkiG9w0BAQsFADAgMR4wHAYDVQQDExVBQ1MyQ2xpZW50Q2VydGlmaWNhdGUwHhcNMTIwNTIyMj
IxMTIyWhcNMzAwNTIyMDcwMDAwWjAgMR4wHAYDVQQDExVBQ1MyQ2xpZW50Q2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCh7HjK
YyVMDZDT64OgtcGKWxHmK2wqzi2LJb65KxGdNfObWGxh5HQtjzrgHDkACPsgyYseqxhGxHh8I/TR6wBKx/AAKuPHE8jB4hJ1W6FczPfb7FaMV9xP0qNQrbNGZU
YbCdy7U5zIw4XrGq22l6yTqpCAh59DLufd4d7x8fCgUDV3l1ZwrncF0QrBRzns/O9Ex9pXsi2DzMa1S1PKR81D9q5QSW7LZkCgSSqI6W0b5iodx/a3RBvW3l7d
noW2fPqkZ4iMcntGNqgsSGtbXPvUR3fFdjmg+xq9FfqWyNxShlZg4U+wE1v4+kzTJxd9sgD1V0PKgW57zyzdOmTyFPJFAgMBAAGjVTBTMFEGA1UdAQRKMEiAEM
9qihCt+12P5FrjVMAEYjShIjAgMR4wHAYDVQQDExVBQ1MyQ2xpZW50Q2VydGlmaWNhdGWCEDOjRWDQSfa3ToivgvdWcxQwDQYJKoZIhvcNAQELBQADggEBAIm6
gBOkSdYjXgOvcJGgE4FJkKAMQzAhkdYq5+stfUotG6vZNL3nVOOA6aELMq/ENhrJLC3rTwLOIgj4Cy+B7BxUS9GxTPphneuZCBzjvqhzP5DmLBs8l8qu10XAsh
y1NFZmB24rMoq8C+HPOpuVLzkwBr+qcCq7ry2326auogvVMGaxhHlwSLR4Q1OhRjKs8JctCk2+5Qs1NHfawa7jWHxdAK6cLm7Rv/c0ig2Jow7wRaI5ciAcEjX7
m1t9gRT1mNeeluL4cZa6WyVXqXc6U2wfR5DY6GOMUubN5Nr1n8Czew8TPfab4OG37BuEMNmBpqoRrRgFnDzVtItOnhuFTa0=";

public static string Defaultx5cValue
{
get
{
return Regex.Replace(_defaultx5cValue, @"\r\n?|\n", String.Empty);
}
}

public static readonly IDictionary<string, string> ExtraQueryParams
= new Dictionary<string, string>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@


#if !ANDROID && !iOS && !WINDOWS_APP
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Cache.Keys;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.UI;
using Microsoft.Identity.Client.Utils;
using Microsoft.Identity.Test.Common;
using Microsoft.Identity.Test.Common.Core.Mocks;
using Microsoft.Identity.Test.Common.Mocks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using static Microsoft.Identity.Client.Internal.JsonWebToken;

Expand All @@ -24,6 +28,15 @@ namespace Microsoft.Identity.Test.Unit
[DeploymentItem(@"Resources\testCert.crtfile")]
public class ConfidentialClientWithCertTests : TestBase
{
private TokenCacheHelper _tokenCacheHelper;

[TestInitialize]
public override void TestInitialize()
{
base.TestInitialize();
_tokenCacheHelper = new TokenCacheHelper();
}

private static MockHttpMessageHandler CreateTokenResponseHttpHandlerWithX5CValidation(bool clientCredentialFlow)
{
return new MockHttpMessageHandler()
Expand All @@ -38,30 +51,38 @@ private static MockHttpMessageHandler CreateTokenResponseHttpHandlerWithX5CValid
// Check presence of client_assertion in request
Assert.IsTrue(formsData.TryGetValue("client_assertion", out string encodedJwt), "Missing client_assertion from request");
// Check presence of x5c cert claim. It should exist.
// Check presence and value of x5c cert claim.
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadJwtToken(encodedJwt);
Assert.IsTrue(jsonToken.Header.Any(header => header.Key == "x5c"), "x5c should be present");
var x5c = jsonToken.Header.Where(header => header.Key == "x5c").FirstOrDefault();
Assert.AreEqual("x5c", x5c.Key, "x5c should be present");
Assert.AreEqual(x5c.Value.ToString(), MsalTestConstants.Defaultx5cValue);
}
};
}

private static HttpResponseMessage CreateResponse(bool clientCredentialFlow)
{
return clientCredentialFlow ?
MockHelpers.CreateSuccessfulClientCredentialTokenResponseMessage() :
MockHelpers.CreateSuccessfulClientCredentialTokenResponseMessage(MockHelpers.CreateClientInfo(MsalTestConstants.Uid, MsalTestConstants.Utid)) :
MockHelpers.CreateSuccessTokenResponseMessage(
MsalTestConstants.Scope.AsSingleString(),
MockHelpers.CreateIdToken(MsalTestConstants.UniqueId, MsalTestConstants.DisplayableId),
MockHelpers.CreateClientInfo(MsalTestConstants.Uid, MsalTestConstants.Utid + "more"));
MockHelpers.CreateClientInfo(MsalTestConstants.Uid, MsalTestConstants.Utid));
}

internal void SetupMocks(MockHttpManager httpManager)
private void SetupMocks(MockHttpManager httpManager)
{
httpManager.AddInstanceDiscoveryMockHandler();
httpManager.AddMockHandlerForTenantEndpointDiscovery(MsalTestConstants.AuthorityCommonTenant);
}

private void SetupMocks(MockHttpManager httpManager, string authority)
{
httpManager.AddInstanceDiscoveryMockHandler();
httpManager.AddMockHandlerForTenantEndpointDiscovery(authority);
}

[TestMethod]
[Description("Test for client assertion with X509 public certificate using sendCertificate")]
public async Task JsonWebTokenWithX509PublicCertSendCertificateTestAsync()
Expand All @@ -73,12 +94,12 @@ public async Task JsonWebTokenWithX509PublicCertSendCertificateTestAsync()
ResourceHelper.GetTestResourceRelativePath("valid_cert.pfx"),
MsalTestConstants.DefaultPassword);

var app = ConfidentialClientApplicationBuilder.Create(MsalTestConstants.ClientId)
.WithAuthority(
new System.Uri(ClientApplicationBase.DefaultAuthority),
true).WithRedirectUri(MsalTestConstants.RedirectUri)
.WithHttpManager(harness.HttpManager)
.WithCertificate(certificate).BuildConcrete();
var app = ConfidentialClientApplicationBuilder
.Create(MsalTestConstants.ClientId)
.WithAuthority(new System.Uri(ClientApplicationBase.DefaultAuthority),true)
.WithRedirectUri(MsalTestConstants.RedirectUri)
.WithHttpManager(harness.HttpManager)
.WithCertificate(certificate).BuildConcrete();

//Check for x5c claim
harness.HttpManager.AddMockHandler(CreateTokenResponseHttpHandlerWithX5CValidation(true));
Expand All @@ -96,6 +117,12 @@ public async Task JsonWebTokenWithX509PublicCertSendCertificateTestAsync()
.ConfigureAwait(false);

Assert.IsNotNull(result.AccessToken);

//Check app cache
Assert.AreEqual(1, app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().Count());

//Clear cache
app.AppTokenCacheInternal.ClearMsalCache();
}
}

Expand Down Expand Up @@ -136,6 +163,60 @@ public async Task JsonWebTokenWithX509PublicCertSendCertificateOnBehalfOfTestAsy
.ConfigureAwait(false);

Assert.IsNotNull(result.AccessToken);

//Check user cache
Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count());

//Clear cache
app.UserTokenCacheInternal.ClearMsalCache();
}
}

[TestMethod]
[Description("Test for acqureTokenSilent with X509 public certificate using sendCertificate")]
public async Task JsonWebTokenWithX509PublicCertSendCertificateSilentTestAsync()
{
using (var harness = CreateTestHarness())
{
SetupMocks(harness.HttpManager, "https://login.microsoftonline.com/my-utid/");
var certificate = new X509Certificate2(
ResourceHelper.GetTestResourceRelativePath("valid_cert.pfx"),
MsalTestConstants.DefaultPassword);

var app = ConfidentialClientApplicationBuilder
.Create(MsalTestConstants.ClientId)
.WithAuthority(new System.Uri("https://login.microsoftonline.com/my-utid"),true)
.WithRedirectUri(MsalTestConstants.RedirectUri)
.WithHttpManager(harness.HttpManager)
.WithCertificate(certificate).BuildConcrete();

_tokenCacheHelper.PopulateCacheWithOneAccessToken(app.UserTokenCacheInternal.Accessor);
app.UserTokenCacheInternal.Accessor.DeleteAccessToken(
new MsalAccessTokenCacheKey(
MsalTestConstants.ProductionPrefNetworkEnvironment,
MsalTestConstants.Utid,
MsalTestConstants.UserIdentifier,
MsalTestConstants.ClientId,
MsalTestConstants.ScopeForAnotherResourceStr));

//Check for x5c claim
harness.HttpManager.AddMockHandler(CreateTokenResponseHttpHandlerWithX5CValidation(false));

var result = await app
.AcquireTokenSilent(
new[] { "someTestScope"},
new Account(MsalTestConstants.UserIdentifier, MsalTestConstants.DisplayableId, null))
.WithSendX5C(true)
.WithForceRefresh(true)
.ExecuteAsync(CancellationToken.None).ConfigureAwait(false);

Assert.IsNotNull(result.AccessToken);

//Check user cache
Assert.AreEqual(1, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count());

//Clear cache
app.UserTokenCacheInternal.ClearMsalCache();
}
}

Expand Down

0 comments on commit 7bec7a4

Please sign in to comment.