Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing sendX5C on silent client credential call #1169

Merged
merged 4 commits into from
May 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
trwalke marked this conversation as resolved.
Show resolved Hide resolved
new MsalAccessTokenCacheKey(
MsalTestConstants.ProductionPrefNetworkEnvironment,
MsalTestConstants.Utid,
MsalTestConstants.UserIdentifier,
MsalTestConstants.ClientId,
MsalTestConstants.ScopeForAnotherResourceStr));

//Check for x5c claim
harness.HttpManager.AddMockHandler(CreateTokenResponseHttpHandlerWithX5CValidation(false));
trwalke marked this conversation as resolved.
Show resolved Hide resolved

var result = await app
.AcquireTokenSilent(
new[] { "someTestScope"},
new Account(MsalTestConstants.UserIdentifier, MsalTestConstants.DisplayableId, null))
.WithSendX5C(true)
trwalke marked this conversation as resolved.
Show resolved Hide resolved
.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