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

Use user-provided values as Utf8 #2492

Draft
wants to merge 12 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 10 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 @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json;
using Microsoft.IdentityModel.Logging;
using Microsoft.IdentityModel.Tokens;
Expand Down Expand Up @@ -78,8 +79,10 @@ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan<byte> byteSpan)
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Iss))
{
_iss = JsonSerializerPrimitives.ReadString(ref reader, JwtRegisteredClaimNames.Iss, ClassName, true);
claims[JwtRegisteredClaimNames.Iss] = _iss;

IssuerUtf8 = JsonSerializerPrimitives.ReadStringUtf8(ref reader, JwtRegisteredClaimNames.Jti, ClassName, true).ToArray();
pmaytak marked this conversation as resolved.
Show resolved Hide resolved
claims[JwtRegisteredClaimNames.Iss] = Encoding.UTF8.GetString(IssuerUtf8.ToArray());
reader.Read();
}
else if (reader.ValueTextEquals(JwtPayloadUtf8Bytes.Jti))
{
Expand Down
12 changes: 3 additions & 9 deletions src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public partial class JsonWebToken : SecurityToken
internal long? _iat;
internal DateTime? _iatDateTime;
internal string _id;
internal string _iss;
internal string _jti;
internal string _sub;
internal long? _nbf;
Expand Down Expand Up @@ -1031,14 +1030,9 @@ public DateTime IssuedAt
/// If the 'iss' claim is not found, an empty string is returned.
/// </para>
/// </remarks>
public override string Issuer
{
get
{
_iss ??= Payload.GetStringValue(JwtRegisteredClaimNames.Iss);
return _iss;
}
}
public override string Issuer => Encoding.UTF8.GetString(IssuerUtf8.ToArray());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Encoding.UTF8.GetString with ReadOnlySpan overload is only available on .NET Core 2.1+ (not Net Fx).
    • I assume ToArray allocates a new array.
  • Since IssuerUtf8 can change, we can't "cache" the string representation, so have to convert it to string each time.


internal ReadOnlyMemory<byte> IssuerUtf8 { get; set; }

/// <summary>
/// Gets the 'value' of the 'jti' claim from the payload.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,7 @@ private async ValueTask<TokenValidationResult> ValidateTokenPayloadAsync(JsonWeb

Validators.ValidateLifetime(notBefore, expires, jsonWebToken, validationParameters);
Validators.ValidateAudience(jsonWebToken.Audiences, jsonWebToken, validationParameters);
string issuer = await Validators.ValidateIssuerAsync(jsonWebToken.Issuer, jsonWebToken, validationParameters, configuration).ConfigureAwait(false);
string issuer = await Validators.ValidateIssuerAsync(jsonWebToken.IssuerUtf8, jsonWebToken, validationParameters, configuration).ConfigureAwait(false);

Validators.ValidateTokenReplay(expires, jsonWebToken.EncodedToken, validationParameters);
if (validationParameters.ValidateActor && !string.IsNullOrWhiteSpace(jsonWebToken.Actor))
Expand Down
16 changes: 15 additions & 1 deletion src/Microsoft.IdentityModel.Tokens/BaseConfiguration.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.Collections.ObjectModel;
using System.Text;
using System.Text.Json.Serialization;

namespace Microsoft.IdentityModel.Tokens
Expand All @@ -12,10 +14,22 @@ namespace Microsoft.IdentityModel.Tokens
/// </summary>
public abstract class BaseConfiguration
{
private string _issuer;

/// <summary>
/// Gets the issuer specified via the metadata endpoint.
/// </summary>
public virtual string Issuer { get; set; }
public virtual string Issuer
{
get => _issuer;
set
{
_issuer = value;
IssuerUtf8 = value != null ? Encoding.UTF8.GetBytes(value) : null;
}
}

internal ReadOnlyMemory<byte> IssuerUtf8 { get; private set; }

/// <summary>
/// Gets the <see cref="ICollection{SecurityKey}"/> that the IdentityProvider indicates are to be used in order to sign tokens.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,19 @@ internal static string ReadString(ref Utf8JsonReader reader, string propertyName
return retval;
}

internal static ReadOnlySpan<byte> ReadStringUtf8(ref Utf8JsonReader reader, string propertyName, string className, bool read = false)
{
// returning null keeps the same logic as JsonSerialization.ReadObject
if (IsReaderPositionedOnNull(ref reader, read, true))
return null;

if (!IsReaderAtTokenType(ref reader, JsonTokenType.String, false))
throw LogHelper.LogExceptionMessage(
CreateJsonReaderExceptionInvalidType(ref reader, "JsonTokenType.StartArray", className, propertyName));

return reader.ValueSpan;
}

internal static string ReadStringAsBool(ref Utf8JsonReader reader, string propertyName, string className, bool read = false)
{
// The parameter 'read' can be used by callers reader position the reader to the next token.
Expand Down
17 changes: 15 additions & 2 deletions src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.IdentityModel.Logging;
Expand Down Expand Up @@ -112,7 +113,7 @@ namespace Microsoft.IdentityModel.Tokens
/// <remarks>The delegate should return a non null string that represents the 'issuer'. If null a default value will be used.
/// <see cref="IssuerValidatorAsync"/> if set, will be called before <see cref="IssuerSigningKeyValidatorUsingConfiguration"/> or <see cref="IssuerSigningKeyValidator"/>
/// </remarks>
internal delegate ValueTask<string> IssuerValidatorAsync(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters);
internal delegate ValueTask<string> IssuerValidatorAsync(ReadOnlyMemory<byte> issuer, SecurityToken securityToken, TokenValidationParameters validationParameters);

/// <summary>
/// Definition for LifetimeValidator.
Expand Down Expand Up @@ -893,11 +894,23 @@ public string RoleClaimType
/// </summary>
public IEnumerable<string> ValidAudiences { get; set; }

private string _validIssuer;

/// <summary>
/// Gets or sets a <see cref="string"/> that represents a valid issuer that will be used to check against the token's issuer.
/// The default is <c>null</c>.
/// </summary>
public string ValidIssuer { get; set; }
public string ValidIssuer
pmaytak marked this conversation as resolved.
Show resolved Hide resolved
{
get => _validIssuer;
set
{
_validIssuer = value;
ValidIssuerUtf8 = value != null ? Encoding.UTF8.GetBytes(value) : null;
}
}

internal ReadOnlyMemory<byte> ValidIssuerUtf8 { get; private set; }

/// <summary>
/// Gets or sets the <see cref="IEnumerable{String}"/> that contains valid issuers that will be used to check against the token's issuer.
Expand Down
15 changes: 9 additions & 6 deletions src/Microsoft.IdentityModel.Tokens/Validators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Abstractions;
using Microsoft.IdentityModel.Logging;
Expand Down Expand Up @@ -219,7 +220,7 @@ public static string ValidateIssuer(string issuer, SecurityToken securityToken,
/// <remarks>An EXACT match is required.</remarks>
internal static string ValidateIssuer(string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters, BaseConfiguration configuration)
{
ValueTask<string> vt = ValidateIssuerAsync(issuer, securityToken, validationParameters, configuration);
ValueTask<string> vt = ValidateIssuerAsync(Encoding.UTF8.GetBytes(issuer), securityToken, validationParameters, configuration);
return vt.IsCompletedSuccessfully ?
vt.Result :
vt.AsTask().GetAwaiter().GetResult();
Expand All @@ -228,7 +229,7 @@ internal static string ValidateIssuer(string issuer, SecurityToken securityToken
/// <summary>
/// Determines if an issuer found in a <see cref="SecurityToken"/> is valid.
/// </summary>
/// <param name="issuer">The issuer to validate</param>
/// <param name="issuerUtf8">The issuer to validate</param>
/// <param name="securityToken">The <see cref="SecurityToken"/> that is being validated.</param>
/// <param name="validationParameters"><see cref="TokenValidationParameters"/> required for validation.</param>
/// <param name="configuration">The <see cref="BaseConfiguration"/> required for issuer and signing key validation.</param>
Expand All @@ -240,7 +241,7 @@ internal static string ValidateIssuer(string issuer, SecurityToken securityToken
/// <exception cref="SecurityTokenInvalidIssuerException">If 'issuer' failed to matched either <see cref="TokenValidationParameters.ValidIssuer"/> or one of <see cref="TokenValidationParameters.ValidIssuers"/> or <see cref="BaseConfiguration.Issuer"/>.</exception>
/// <remarks>An EXACT match is required.</remarks>
internal static async ValueTask<string> ValidateIssuerAsync(
string issuer,
ReadOnlyMemory<byte> issuerUtf8,
SecurityToken securityToken,
TokenValidationParameters validationParameters,
BaseConfiguration configuration)
Expand All @@ -249,7 +250,9 @@ internal static async ValueTask<string> ValidateIssuerAsync(
throw LogHelper.LogArgumentNullException(nameof(validationParameters));

if (validationParameters.IssuerValidatorAsync != null)
return await validationParameters.IssuerValidatorAsync(issuer, securityToken, validationParameters).ConfigureAwait(false);
return await validationParameters.IssuerValidatorAsync(issuerUtf8, securityToken, validationParameters).ConfigureAwait(false);

string issuer = issuerUtf8.ToString();

if (validationParameters.IssuerValidatorUsingConfiguration != null)
return validationParameters.IssuerValidatorUsingConfiguration(issuer, securityToken, validationParameters, configuration);
Expand All @@ -276,7 +279,7 @@ internal static async ValueTask<string> ValidateIssuerAsync(

if (configuration != null)
{
if (string.Equals(configuration.Issuer, issuer))
if (string.Equals(configuration.IssuerUtf8, issuerUtf8))
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
Expand All @@ -285,7 +288,7 @@ internal static async ValueTask<string> ValidateIssuerAsync(
}
}

if (string.Equals(validationParameters.ValidIssuer, issuer))
if (validationParameters.ValidIssuerUtf8.Equals(issuerUtf8))
{
if (LogHelper.IsEnabled(EventLogLevel.Informational))
LogHelper.LogInformation(LogMessages.IDX10236, LogHelper.MarkAsNonPII(issuer));
Expand Down
Loading