Skip to content

Commit

Permalink
Adding withCertificate overload to use cert serial number (#5151)
Browse files Browse the repository at this point in the history
* Adding withCertificate overload to use cert serial number

* Updating summary

* Updating new api

* Adding test cases
Refactoring

* Apply suggestions from code review

Co-authored-by: Gladwin Johnson <[email protected]>

* Refactoring

* Updating comments

* Updating tests to reset static caches

---------

Co-authored-by: trwalke <[email protected]>
Co-authored-by: Gladwin Johnson <[email protected]>
  • Loading branch information
3 people authored Feb 25, 2025
1 parent 5587cb3 commit 68dc10f
Show file tree
Hide file tree
Showing 13 changed files with 275 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,17 @@ internal static AcquireTokenForClientParameterBuilder Create(
IConfidentialClientApplicationExecutor confidentialClientApplicationExecutor,
IEnumerable<string> scopes)
{
return new AcquireTokenForClientParameterBuilder(confidentialClientApplicationExecutor).WithScopes(scopes);
var builder = new AcquireTokenForClientParameterBuilder(confidentialClientApplicationExecutor).WithScopes(scopes);

if (!string.IsNullOrEmpty(confidentialClientApplicationExecutor.ServiceBundle.Config.CertificateIdToAssociateWithToken))
{
builder.WithAdditionalCacheKeyComponents(new SortedList<string, string>
{
{ Constants.CertSerialNumber, confidentialClientApplicationExecutor.ServiceBundle.Config.CertificateIdToAssociateWithToken }
});
}

return builder;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public string ClientVersion
public bool IsManagedIdentity { get; }
public bool IsConfidentialClient { get; }
public bool IsPublicClient => !IsConfidentialClient && !IsManagedIdentity;
public string CertificateIdToAssociateWithToken { get; set; }

public Func<AppTokenProviderParameters, Task<AppTokenProviderResult>> AppTokenProvider;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Security.Cryptography.X509Certificates;

namespace Microsoft.Identity.Client.RP
{
/// <summary>
/// Resource Provider extensibility methods for <see cref="ConfidentialClientApplicationBuilder"/>
/// </summary>
#if !SUPPORTS_CONFIDENTIAL_CLIENT
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] // hide confidential client on mobile
#endif
public static class ConfidentialClientApplicationBuilderForResourceProviders
{
/// <summary>
/// Sets the certificate associated with the application.
/// Applicable to first-party applications only, this method also allows to specify
/// if the <see href="https://datatracker.ietf.org/doc/html/rfc7517#section-4.7">x5c claim</see> should be sent to Azure AD.
/// Sending the x5c enables application developers to achieve easy certificate roll-over in Azure AD:
/// this method will send the certificate chain 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). For details see https://aka.ms/msal-net-sni
/// This API allow you to associate the tokens acquired from Azure AD with the certificate serial number.
/// This can be used to partition the cache by certificate. Tokens acquired with one certificate will not be accessible to another certificate with a different serial number.
/// </summary>
/// <param name="builder"></param>
/// <param name="certificate">The X509 certificate used as credentials to prove the identity of the application to Azure AD.</param>
/// <param name="sendX5C">To send X5C with every request or not. The default is <c>false</c></param>
/// <param name="associateTokensWithCertificateSerialNumber">Determines if the application tokens acquired from Azure AD are associated with the certificate serial number</param>
/// <remarks>You should use certificates with a private key size of at least 2048 bytes. Future versions of this library might reject certificates with smaller keys. </remarks>
public static ConfidentialClientApplicationBuilder WithCertificate(
this ConfidentialClientApplicationBuilder builder,
X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber)
{
builder.WithCertificate(certificate, sendX5C);

if (associateTokensWithCertificateSerialNumber)
{
builder.Config.CertificateIdToAssociateWithToken = certificate.SerialNumber;
}

return builder;
}
}
}
1 change: 1 addition & 0 deletions src/client/Microsoft.Identity.Client/Internal/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ internal static class Constants
public const string ManagedIdentityDefaultClientId = "system_assigned_managed_identity";
public const string ManagedIdentityDefaultTenant = "managed_identity";
public const string CiamAuthorityHostSuffix = ".ciamlogin.com";
public const string CertSerialNumber = "cert_sn";

public const int CallerSdkIdMaxLength = 10;
public const int CallerSdkVersionMaxLength = 20;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
<Compile Include="$(PathToMsalSources)\**\*.cs" Exclude="$(PathToMsalSources)\obj\**\*.*" />
<Compile Remove="$(PathToMsalSources)\Platforms\**\*.*;$(PathToMsalSources)\Resources\*.cs" />
<Compile Remove="$(PathToMsalSources)\PlatformsCommon\PlatformNotSupported\ApiConfig\SystemWebViewOptions.cs" />
<None Remove="Extensibility\RP\ConfidentialClientApplicationBuilderForResourceProviders.cs" />
<EmbeddedResource Include="$(PathToMsalSources)\Properties\Microsoft.Identity.Client.rd.xml" />
<None Include="$(PathToMsalSources)\..\..\..\README.md" Pack="true" PackagePath="\" />
<None Include="Platforms\net\JsonObjectAttribute.cs" />
Expand Down Expand Up @@ -163,4 +164,8 @@
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Shipped.txt" />
<AdditionalFiles Include="PublicAPI/$(TargetFramework)/PublicAPI.Unshipped.txt" />
</ItemGroup>

<ItemGroup>
<Compile Include="Extensibility\RP\ConfidentialClientApplicationBuilderForResourceProviders.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Microsoft.Identity.Client.BrokerOptions.OperatingSystems.Linux = 2 -> Microsoft.Identity.Client.BrokerOptions.OperatingSystems
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.get -> System.Collections.Generic.IEnumerable<string>
Microsoft.Identity.Client.AssertionRequestOptions.ClientCapabilities.set -> void
Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders
static Microsoft.Identity.Client.RP.ConfidentialClientApplicationBuilderForResourceProviders.WithCertificate(this Microsoft.Identity.Client.ConfidentialClientApplicationBuilder builder, System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, bool sendX5C, bool associateTokensWithCertificateSerialNumber) -> Microsoft.Identity.Client.ConfidentialClientApplicationBuilder
11 changes: 8 additions & 3 deletions tests/Microsoft.Identity.Test.Unit/Helpers/CertHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ public static class CertHelper
{
private static Dictionary<KnownTestCertType, X509Certificate2> s_x509Certificates = new Dictionary<KnownTestCertType, X509Certificate2>();

public static X509Certificate2 GetOrCreateTestCert(KnownTestCertType knownTestCertType = KnownTestCertType.RSA)
public static X509Certificate2 GetOrCreateTestCert(KnownTestCertType knownTestCertType = KnownTestCertType.RSA, bool regenerateCert = false)
{
// create the cert if it doesn't exist. use a lock to prevent multiple threads from creating the cert
s_x509Certificates.TryGetValue(knownTestCertType, out X509Certificate2 x509Certificate2);

if (x509Certificate2 == null)
if (x509Certificate2 == null || regenerateCert)
{
lock (typeof(CertHelper))
{
if (x509Certificate2 == null)
if (x509Certificate2 != null)
{
x509Certificate2 = CreateTestCert(knownTestCertType);
s_x509Certificates[knownTestCertType] = x509Certificate2;
}
else
{
x509Certificate2 = CreateTestCert(knownTestCertType);
s_x509Certificates.Add(knownTestCertType, x509Certificate2);
Expand Down
Loading

0 comments on commit 68dc10f

Please sign in to comment.