Skip to content

Commit 3701e17

Browse files
committed
Fix MSAuth + x5c
1 parent 212c1fb commit 3701e17

File tree

4 files changed

+70
-22
lines changed

4 files changed

+70
-22
lines changed

src/Microsoft.Identity.Web.TokenAcquisition/MsAuth10AtPop.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ internal static AcquireTokenForClientParameterBuilder WithAtPop(
1919
X509Certificate2 clientCertificate,
2020
string popPublicKey,
2121
string jwkClaim,
22-
string clientId)
22+
string clientId,
23+
bool sendX5C)
2324
{
2425
_ = Throws.IfNull(popPublicKey);
2526
_ = Throws.IfNull(jwkClaim);
@@ -31,10 +32,12 @@ internal static AcquireTokenForClientParameterBuilder WithAtPop(
3132
clientCertificate,
3233
data.RequestUri.AbsoluteUri,
3334
jwkClaim,
34-
clientId);
35+
clientId,
36+
sendX5C);
3537

3638
data.BodyParameters.Remove("client_assertion");
3739
data.BodyParameters.Add("request", signedAssertion);
40+
3841
return Task.CompletedTask;
3942
});
4043

@@ -45,7 +48,8 @@ internal static AcquireTokenForClientParameterBuilder WithAtPop(
4548
X509Certificate2 certificate,
4649
string audience,
4750
string jwkClaim,
48-
string clientId)
51+
string clientId,
52+
bool sendX5C)
4953
{
5054
// no need to add exp, nbf as JsonWebTokenHandler will add them by default
5155
var claims = new Dictionary<string, object>()
@@ -57,12 +61,24 @@ internal static AcquireTokenForClientParameterBuilder WithAtPop(
5761
{ "pop_jwk", jwkClaim }
5862
};
5963

64+
var signingCredentials = new X509SigningCredentials(certificate);
6065
var securityTokenDescriptor = new SecurityTokenDescriptor
61-
{
66+
{
6267
Claims = claims,
63-
SigningCredentials = new X509SigningCredentials(certificate)
68+
SigningCredentials = signingCredentials
6469
};
6570

71+
if (sendX5C)
72+
{
73+
if (signingCredentials.Key is X509SecurityKey x509SecurityKey)
74+
{
75+
string x5cValue = Convert.ToBase64String(x509SecurityKey.Certificate.GetRawCertData());
76+
77+
// Does not seem to work?!
78+
securityTokenDescriptor.AdditionalHeaderClaims = new Dictionary<string, object>() { { "x5c", x5cValue } };
79+
}
80+
}
81+
6682
var handler = new JsonWebTokenHandler();
6783
return handler.CreateToken(securityTokenDescriptor);
6884
}

src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,8 @@ public async Task<AuthenticationResult> GetAuthenticationResultForAppAsync(
435435
application.AppConfig.ClientCredentialCertificate,
436436
tokenAcquisitionOptions.PopPublicKey!,
437437
tokenAcquisitionOptions.PopClaim!,
438-
application.AppConfig.ClientId);
438+
application.AppConfig.ClientId,
439+
mergedOptions.SendX5C);
439440
}
440441
}
441442
}

tests/Microsoft.Identity.Web.Test.Common/Mocks/MockHttpMessageHandler.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace Microsoft.Identity.Web.Test.Common.Mocks
1212
{
1313
public class MockHttpMessageHandler : HttpMessageHandler
1414
{
15-
public Func<MockHttpMessageHandler, MockHttpMessageHandler> ReplaceMockHttpMessageHandler;
15+
public Func<MockHttpMessageHandler, MockHttpMessageHandler> ReplaceMockHttpMessageHandler { get; set; }
1616

1717
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
1818
public MockHttpMessageHandler()
@@ -53,14 +53,17 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
5353
if (uri.AbsoluteUri.Contains("/discovery/instance"))
5454
#endif
5555
{
56-
ReplaceMockHttpMessageHandler(this);
57-
58-
var responseMessage = new HttpResponseMessage(HttpStatusCode.OK)
56+
if (ReplaceMockHttpMessageHandler != null)
5957
{
60-
Content = new StringContent(TestConstants.DiscoveryJsonResponse),
61-
};
58+
ReplaceMockHttpMessageHandler(this);
59+
60+
var responseMessage = new HttpResponseMessage(HttpStatusCode.OK)
61+
{
62+
Content = new StringContent(TestConstants.DiscoveryJsonResponse),
63+
};
6264

63-
return Task.FromResult(responseMessage);
65+
return Task.FromResult(responseMessage);
66+
}
6467
}
6568

6669
if (!string.IsNullOrEmpty(ExpectedUrl))

tests/Microsoft.Identity.Web.Test/MsAuth10AtPopTests.cs

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,49 @@
33

44
using System;
55
using System.Security.Cryptography.X509Certificates;
6+
using System.Threading.Tasks;
67
using Microsoft.Identity.Client;
78
using Microsoft.Identity.Web.Test.Common;
9+
using Microsoft.Identity.Web.Test.Common.Mocks;
810
using Xunit;
911

1012
namespace Microsoft.Identity.Web.Test
1113
{
1214
public class MsAuth10AtPopTests
1315
{
1416
[Fact]
15-
public void MsAuth10AtPop_WithAtPop_ShouldPopulateBuilderWithProofOfPosessionKeyIdAndOnBeforeTokenRequestTest()
17+
public async Task MsAuth10AtPop_WithAtPop_ShouldPopulateBuilderWithProofOfPosessionKeyIdAndOnBeforeTokenRequestTestAsync()
1618
{
1719
// Arrange
18-
var builder = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId);
19-
builder.WithExperimentalFeatures();
20-
builder.WithClientSecret(TestConstants.ClientSecret);
21-
var app = builder.Build();
22-
var clientCertificate = new X509Certificate2(new byte[0]);
20+
21+
MockHttpClientFactory mockHttpClientFactory = new MockHttpClientFactory();
22+
var tokenHandler = MockHttpCreator.CreateClientCredentialTokenHandler();
23+
mockHttpClientFactory.AddMockHandler(MockHttpCreator.CreateInstanceDiscoveryMockHandler());
24+
mockHttpClientFactory.AddMockHandler(tokenHandler);
25+
26+
var certificateDescription = CertificateDescription.FromBase64Encoded(
27+
TestConstants.CertificateX5cWithPrivateKey,
28+
TestConstants.CertificateX5cWithPrivateKeyPassword);
29+
ICertificateLoader loader = new DefaultCertificateLoader();
30+
loader.LoadIfNeeded(certificateDescription);
31+
32+
Assert.NotNull(certificateDescription.Certificate);
33+
34+
var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId)
35+
.WithExperimentalFeatures()
36+
.WithCertificate(certificateDescription.Certificate)
37+
.WithHttpClientFactory(mockHttpClientFactory)
38+
.Build();
39+
40+
2341
var popPublicKey = "pop_key";
2442
var jwkClaim = "jwk_claim";
2543
var clientId = "client_id";
2644

2745
// Act
28-
var result = MsAuth10AtPop.WithAtPop(app.AcquireTokenForClient(new[] { TestConstants.Scopes }), clientCertificate, popPublicKey, jwkClaim, clientId);
46+
AuthenticationResult result = await app.AcquireTokenForClient(new[] { TestConstants.Scopes })
47+
.WithAtPop(certificateDescription.Certificate, popPublicKey, jwkClaim, clientId, true)
48+
.ExecuteAsync();
2949

3050
// Assert
3151
Assert.NotNull(result);
@@ -41,7 +61,13 @@ public void MsAuth10AtPop_ThrowsWithNullPopKeyTest()
4161
var clientId = "client_id";
4262

4363
// Act & Assert
44-
Assert.Throws<ArgumentNullException>(() => MsAuth10AtPop.WithAtPop(app.AcquireTokenForClient(new[] { TestConstants.Scopes }), clientCertificate, string.Empty, jwkClaim, clientId));
64+
Assert.Throws<ArgumentNullException>(() => MsAuth10AtPop.WithAtPop(
65+
app.AcquireTokenForClient(new[] { TestConstants.Scopes }),
66+
clientCertificate,
67+
string.Empty,
68+
jwkClaim,
69+
clientId,
70+
true));
4571
}
4672

4773
[Fact]
@@ -55,7 +81,9 @@ public void MsAuth10AtPop_ThrowsWithNullJwkClaimTest()
5581

5682
// Act & Assert
5783
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
58-
Assert.Throws<ArgumentNullException>(() => MsAuth10AtPop.WithAtPop(app.AcquireTokenForClient(new[] { TestConstants.Scopes }), clientCertificate, popPublicKey, null, clientId));
84+
Assert.Throws<ArgumentNullException>(() => MsAuth10AtPop.WithAtPop(
85+
app.AcquireTokenForClient(new[] { TestConstants.Scopes }),
86+
clientCertificate, popPublicKey, null, clientId, true));
5987
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
6088
}
6189

0 commit comments

Comments
 (0)