Skip to content
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 @@ -683,7 +683,7 @@ internal JsonClaimSet CreateClaimSet(ReadOnlySpan<char> strSpan, int startIndex,
const int MaxStackallocThreshold = 256;
Span<byte> output = outputSize <= MaxStackallocThreshold
? stackalloc byte[outputSize]
: (rented = ArrayPool<byte>.Shared.Rent(outputSize));
: (rented = ArrayPool<byte>.Shared.Rent(outputSize)).AsSpan(0, outputSize);

try
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,13 +231,13 @@ int sizeOfEncodedHeaderAndPayloadAsciiBytes
finally
{
if (encodedChars is not null)
ArrayPool<char>.Shared.Return(encodedChars);
ArrayPool<char>.Shared.Return(encodedChars, clearArray: true);
#if NET6_0_OR_GREATER
if (signatureBytes is not null)
ArrayPool<byte>.Shared.Return(signatureBytes);
ArrayPool<byte>.Shared.Return(signatureBytes, clearArray: true);
#endif
if (asciiBytes is not null)
ArrayPool<byte>.Shared.Return(asciiBytes);
ArrayPool<byte>.Shared.Return(asciiBytes, clearArray: true);

writer?.Dispose();
}
Expand Down Expand Up @@ -573,16 +573,16 @@ int sizeOfEncodedHeaderAndPayloadAsciiBytes
finally
{
if (encodedChars is not null)
ArrayPool<char>.Shared.Return(encodedChars);
ArrayPool<char>.Shared.Return(encodedChars, clearArray: true);
#if NET6_0_OR_GREATER
if (signatureBytes is not null)
ArrayPool<byte>.Shared.Return(signatureBytes);
ArrayPool<byte>.Shared.Return(signatureBytes, clearArray: true);
#endif
if (asciiBytes is not null)
ArrayPool<byte>.Shared.Return(asciiBytes);
ArrayPool<byte>.Shared.Return(asciiBytes, clearArray: true);

if (payloadBytes is not null)
ArrayPool<byte>.Shared.Return(payloadBytes);
ArrayPool<byte>.Shared.Return(payloadBytes, clearArray: true);

writer?.Dispose();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public byte[] Decompress(byte[] value)
finally
{
if (chars != null)
ArrayPool<char>.Shared.Return(chars);
ArrayPool<char>.Shared.Return(chars, clearArray: true);
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/Microsoft.IdentityModel.Tokens/EncodingUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ internal static T PerformEncodingDependentOperation<T>(
}
finally
{
ArrayPool<byte>.Shared.Return(bytes);
ArrayPool<byte>.Shared.Return(bytes, clearArray: true);
}
}

Expand Down Expand Up @@ -108,7 +108,7 @@ internal static T PerformEncodingDependentOperation<T, TX, TY, TZ>(
}
finally
{
ArrayPool<byte>.Shared.Return(bytes);
ArrayPool<byte>.Shared.Return(bytes, clearArray: true);
}
}

Expand Down Expand Up @@ -171,7 +171,7 @@ internal static T PerformEncodingDependentOperation<T, TX>(
}
finally
{
ArrayPool<byte>.Shared.Return(bytes);
ArrayPool<byte>.Shared.Return(bytes, clearArray: true);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using Xunit;

namespace Microsoft.IdentityModel.JsonWebTokens.Tests
{
/// <summary>
/// Exercises the buffer handling path used by JsonWebToken claim-set decoding,
/// covering both the stack-allocated and pooled buffer code paths.
/// </summary>
public class ClaimSetBufferHandlingTests
{
/// <summary>
/// A payload larger than the stackalloc threshold uses the pooled buffer path.
/// All decoded claim values must round-trip exactly.
/// </summary>
[Fact]
public void LargePayload_PooledBufferPath_ClaimsRoundTrip()
{
var claims = new Dictionary<string, object>
{
["sub"] = "user@example.com",
["name"] = "A deliberately long claim value used to exercise the pooled buffer path",
["role"] = "administrator",
["department"] = "Engineering - additional padding to grow the encoded payload",
["description"] = new string('X', 200)
};

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(new string('k', 128)));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var descriptor = new SecurityTokenDescriptor
{
SigningCredentials = creds,
Claims = claims,
Issuer = "https://test-issuer.example.com",
Audience = "https://test-audience.example.com",
};

var handler = new JsonWebTokenHandler();
string tokenString = handler.CreateToken(descriptor);

var jwt = new JsonWebToken(tokenString);

Assert.Equal("user@example.com", jwt.Claims.First(c => c.Type == "sub").Value);
Assert.Equal("A deliberately long claim value used to exercise the pooled buffer path",
jwt.Claims.First(c => c.Type == "name").Value);
Assert.Equal("administrator", jwt.Claims.First(c => c.Type == "role").Value);
Assert.Equal("Engineering - additional padding to grow the encoded payload",
jwt.Claims.First(c => c.Type == "department").Value);
Assert.Equal(new string('X', 200),
jwt.Claims.First(c => c.Type == "description").Value);
}

/// <summary>
/// A small payload takes the stack-allocated path.
/// Regression check: parsing must still produce the expected claim values.
/// </summary>
[Fact]
public void SmallPayload_StackallocPath_ClaimsRoundTrip()
{
var claims = new Dictionary<string, object>
{
["sub"] = "alice",
["role"] = "user"
};

var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(new string('k', 128)));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var descriptor = new SecurityTokenDescriptor
{
SigningCredentials = creds,
Claims = claims,
Issuer = "https://issuer.example.com",
Audience = "https://audience.example.com",
};

var handler = new JsonWebTokenHandler();
string tokenString = handler.CreateToken(descriptor);

var jwt = new JsonWebToken(tokenString);

Assert.Equal("alice", jwt.Claims.First(c => c.Type == "sub").Value);
Assert.Equal("user", jwt.Claims.First(c => c.Type == "role").Value);
Assert.Equal("https://issuer.example.com", jwt.Issuer);
Assert.Equal("https://audience.example.com",
jwt.Claims.First(c => c.Type == "aud").Value);
}

/// <summary>
/// Repeatedly parses tokens that take the pooled buffer path to confirm
/// that consecutive uses of pooled buffers do not affect parsing results.
/// </summary>
[Fact]
public void RepeatedParsing_PooledBufferPath_ProducesConsistentResults()
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(new string('k', 128)));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var handler = new JsonWebTokenHandler();

for (int i = 0; i < 20; i++)
{
string uniqueValue = $"iteration-{i}-" + new string((char)('A' + (i % 26)), 200);

var descriptor = new SecurityTokenDescriptor
{
SigningCredentials = creds,
Claims = new Dictionary<string, object>
{
["seq"] = i.ToString(),
["payload"] = uniqueValue
},
Issuer = "https://issuer.example.com",
};

string tokenString = handler.CreateToken(descriptor);
var jwt = new JsonWebToken(tokenString);

Assert.Equal(i.ToString(), jwt.Claims.First(c => c.Type == "seq").Value);
Assert.Equal(uniqueValue, jwt.Claims.First(c => c.Type == "payload").Value);
}
}
}
}