Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
See the [releases](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases) for details on bug fixes and added features.


Pending Next Release
=====
### Bug Fixes:
- Reduced allocations in `AadIssuerValidator` by not using `string.Replace` where appropriate. See issue [#2595](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2595) and PR [#2597](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/pull/2597) for more details.

7.6.0
=====
### New Features:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using static Microsoft.IdentityModel.Validators.AadIssuerValidator;

namespace Microsoft.IdentityModel.Validators
{
Expand Down Expand Up @@ -388,16 +387,46 @@ private static bool IsValidIssuer(string validIssuerTemplate, string tenantId, s
if (string.IsNullOrEmpty(validIssuerTemplate))
return false;

if (validIssuerTemplate.Contains(TenantIdTemplate))
int indexOfTenantIdTemplate = validIssuerTemplate.IndexOf(TenantIdTemplate, StringComparison.Ordinal);
if (indexOfTenantIdTemplate >= 0)
{
return validIssuerTemplate.Replace(TenantIdTemplate, tenantId) == actualIssuer;
return IssuersWithTemplatesAreEqual(validIssuerTemplate.AsSpan(), TenantIdTemplate.AsSpan(), indexOfTenantIdTemplate, actualIssuer.AsSpan(), tenantId.AsSpan());
}
else
{
Comment thread
kllysng marked this conversation as resolved.
Outdated
Comment thread
kllysng marked this conversation as resolved.
Outdated
return validIssuerTemplate == actualIssuer;
}
}
Comment thread
kllysng marked this conversation as resolved.
Outdated

/// <summary>
Comment thread
kllysng marked this conversation as resolved.
Outdated
/// Compare two Issuers with templates without string allocations.
/// This function replaces: issuer1.Replace(issuer1Template, tenantId) == issuer2
/// Example:
/// issuer1 = "https://login.microsoftonline.com/{tenantid}/v2.0"
Comment thread
kllysng marked this conversation as resolved.
Outdated
/// issuer1Template = "{tenantid}"
/// issuer2 = "https://login.microsoftonline.com/12345678/v2.0"
/// tenantId = "12345678"
/// </summary>
internal static bool IssuersWithTemplatesAreEqual(ReadOnlySpan<char> issuer1, ReadOnlySpan<char> issuer1Template, int templateStartIndex, ReadOnlySpan<char> issuer2, ReadOnlySpan<char> tenantId)
Comment thread
kllysng marked this conversation as resolved.
Outdated
{
var issuer2TemplateStartIndex = issuer2.IndexOf(tenantId);
Comment thread
kllysng marked this conversation as resolved.
Outdated
if (templateStartIndex == -1 || issuer2TemplateStartIndex == -1 || templateStartIndex != issuer2TemplateStartIndex)
return false;

// ensure the first part of the issuer1 matches the first part of issuer2
if (issuer1.Slice(0, templateStartIndex).SequenceCompareTo(issuer2.Slice(0, templateStartIndex)) != 0)
Comment thread
kllysng marked this conversation as resolved.
Outdated
return false;

int secondHalfIssuer1StartIndex = templateStartIndex + issuer1Template.Length;
int secondHalfIssuer2StartIndex = templateStartIndex + tenantId.Length;

// ensure the second halves are equal
if (issuer1.Slice(secondHalfIssuer1StartIndex).SequenceCompareTo(issuer2.Slice(secondHalfIssuer2StartIndex)) != 0)
return false;

return true;
}

private void SetEffectiveLKGIssuer(string aadIssuer, ProtocolVersion protocolVersion, TimeSpan lastKnownGoodLifetime)
{
var issuerLKG = new IssuerLastKnownGood
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,31 +76,37 @@ internal static bool ValidateIssuerSigningKey(SecurityKey securityKey, SecurityT
#if NET6_0_OR_GREATER
if (!string.IsNullOrEmpty(tokenIssuer) && !tokenIssuer.Contains(tenantIdFromToken, StringComparison.Ordinal))
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40004, LogHelper.MarkAsNonPII(tokenIssuer), LogHelper.MarkAsNonPII(tenantIdFromToken))));

// creating an effectiveSigningKeyIssuer is required as signingKeyIssuer might contain {tenantid}
var effectiveSigningKeyIssuer = signingKeyIssuer.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken, StringComparison.Ordinal);
var v2TokenIssuer = openIdConnectConfiguration.Issuer?.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken, StringComparison.Ordinal);
#else
if (!string.IsNullOrEmpty(tokenIssuer) && !tokenIssuer.Contains(tenantIdFromToken))
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40004, LogHelper.MarkAsNonPII(tokenIssuer), LogHelper.MarkAsNonPII(tenantIdFromToken))));
#endif

// creating an effectiveSigningKeyIssuer is required as signingKeyIssuer might contain {tenantid}
var effectiveSigningKeyIssuer = signingKeyIssuer.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken);
var v2TokenIssuer = openIdConnectConfiguration.Issuer?.Replace(AadIssuerValidator.TenantIdTemplate, tenantIdFromToken);
#endif
int templateStartIndex = signingKeyIssuer.IndexOf(AadIssuerValidator.TenantIdTemplate, StringComparison.Ordinal);
string effectiveSigningKeyIssuer = templateStartIndex > -1 ? CreateIssuer(signingKeyIssuer, AadIssuerValidator.TenantIdTemplate, tenantIdFromToken, templateStartIndex) : signingKeyIssuer;

// comparing effectiveSigningKeyIssuer with v2TokenIssuer is required as well because of the following scenario:
// comparing effectiveSigningKeyIssuer with v2TokenIssuer is required because of the following scenario:
// 1. service trusts /common/v2.0 endpoint
// 2. service receieves a v1 token that has issuer like sts.windows.net
// 3. signing key issuers will never match sts.windows.net as v1 endpoint doesn't have issuers attached to keys
// v2TokenIssuer is the representation of Token.Issuer (if it was a v2 issuer)
if (effectiveSigningKeyIssuer != tokenIssuer && effectiveSigningKeyIssuer != v2TokenIssuer)
if (!AadIssuerValidator.IssuersWithTemplatesAreEqual(effectiveSigningKeyIssuer.AsSpan(), tenantIdFromToken.AsSpan(), templateStartIndex, tokenIssuer.AsSpan(), tenantIdFromToken.AsSpan())
&& !AadIssuerValidator.IssuersWithTemplatesAreEqual(effectiveSigningKeyIssuer.AsSpan(), tenantIdFromToken.AsSpan(), templateStartIndex, openIdConnectConfiguration.Issuer == null ? [] : openIdConnectConfiguration.Issuer.AsSpan(), tenantIdFromToken.AsSpan()))
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(LogMessages.IDX40005, LogHelper.MarkAsNonPII(tokenIssuer), LogHelper.MarkAsNonPII(effectiveSigningKeyIssuer))));
}

return true;
}

private static string CreateIssuer(string issuer, string tenantIdTemplate, string tenantId, int templateStartIndex)
{
#if NET6_0_OR_GREATER
return string.Concat(issuer.AsSpan(0, templateStartIndex), tenantId, issuer.AsSpan(templateStartIndex + tenantIdTemplate.Length, issuer.Length - tenantIdTemplate.Length - templateStartIndex));
Comment thread
kllysng marked this conversation as resolved.
#else
return string.Concat(issuer.Substring(0, templateStartIndex), tenantId, issuer.Substring(templateStartIndex + tenantIdTemplate.Length));
#endif
}

/// <summary>
/// Validates the issuer signing key certificate.
/// </summary>
Expand Down