diff --git a/CHANGELOG.md b/CHANGELOG.md
index d04985a0f7..0d7d33a1c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,19 @@
See the [releases](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/releases) for details on bug fixes and added features.
+7.3.1
+======
+### Bug Fixes:
+- Replace propertyName with `MetadataName` constant. See issue [#2471](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2471) for details.
+- Fix 6x to 7x regression where mixed cases OIDC json was not correctly process. See [#2404](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2402) and [#2402](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2402) for details.
+
+### Performance Improvements:
+- Update the benchmark configuration. See issue [#2468](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2468).
+
+### Documentation:
+- Update comment for `azp` in `JsonWebToken`. See [#2475](https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/2475) for details.
+- Link to breaking change announcement. See [#2478].
+- Fix typo in log message. See [#2479].
+
7.3.0
======
### New Features:
diff --git a/benchmark/Microsoft.IdentityModel.Benchmarks/Program.cs b/benchmark/Microsoft.IdentityModel.Benchmarks/Program.cs
index d02f609cf8..785f31b22b 100644
--- a/benchmark/Microsoft.IdentityModel.Benchmarks/Program.cs
+++ b/benchmark/Microsoft.IdentityModel.Benchmarks/Program.cs
@@ -24,6 +24,10 @@ public static void Main(string[] args)
}
private static void DebugThroughTests()
{
+ ReadJWETokenTests readTokenTests = new ReadJWETokenTests();
+ readTokenTests.Setup();
+ readTokenTests.ReadJWE_FromMemory();
+
AsymmetricAdapterSignatures asymmetricAdapter = new AsymmetricAdapterSignatures();
asymmetricAdapter.Setup();
asymmetricAdapter.SignDotnetCreatingBufferRSA();
diff --git a/benchmark/Microsoft.IdentityModel.Benchmarks/ReadJWETokenTests.cs b/benchmark/Microsoft.IdentityModel.Benchmarks/ReadJWETokenTests.cs
new file mode 100644
index 0000000000..a6a3bf4d02
--- /dev/null
+++ b/benchmark/Microsoft.IdentityModel.Benchmarks/ReadJWETokenTests.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using BenchmarkDotNet.Attributes;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Tokens;
+
+namespace Microsoft.IdentityModel.Benchmarks
+{
+ // dotnet run -c release -f net8.0 --filter Microsoft.IdentityModel.Benchmarks.ReadTokenTests*
+
+ [Config(typeof(BenchmarkConfig))]
+ [HideColumns("Type", "Job", "WarmupCount", "LaunchCount")]
+ [MemoryDiagnoser]
+ public class ReadJWETokenTests
+ {
+ string _encryptedJWE;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ var jsonWebTokenHandler = new JsonWebTokenHandler();
+ var jweTokenDescriptor = new SecurityTokenDescriptor
+ {
+ SigningCredentials = BenchmarkUtils.SigningCredentialsRsaSha256,
+ EncryptingCredentials = BenchmarkUtils.EncryptingCredentialsAes256Sha512,
+ TokenType = JwtHeaderParameterNames.Jwk,
+ Claims = BenchmarkUtils.Claims
+ };
+
+ _encryptedJWE = jsonWebTokenHandler.CreateToken(jweTokenDescriptor);
+ }
+
+ [Benchmark]
+ public JsonWebToken ReadJWE_FromString()
+ {
+ return new JsonWebToken(_encryptedJWE);
+ }
+
+ [Benchmark]
+ public JsonWebToken ReadJWE_FromMemory()
+ {
+ return new JsonWebToken(_encryptedJWE.AsMemory());
+ }
+ }
+}
diff --git a/benchmark/Microsoft.IdentityModel.Benchmarks/ReadJWSTokenTests.cs b/benchmark/Microsoft.IdentityModel.Benchmarks/ReadJWSTokenTests.cs
new file mode 100644
index 0000000000..5ffc11c95d
--- /dev/null
+++ b/benchmark/Microsoft.IdentityModel.Benchmarks/ReadJWSTokenTests.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using BenchmarkDotNet.Attributes;
+using Microsoft.IdentityModel.JsonWebTokens;
+using Microsoft.IdentityModel.Tokens;
+
+namespace Microsoft.IdentityModel.Benchmarks
+{
+ // dotnet run -c release -f net8.0 --filter Microsoft.IdentityModel.Benchmarks.ReadJWSTokenTests*
+
+ [Config(typeof(BenchmarkConfig))]
+ [HideColumns("Type", "Job", "WarmupCount", "LaunchCount")]
+ [MemoryDiagnoser]
+ [RankColumn]
+ public class ReadJWSTokenTests
+ {
+ string _encodedJWS;
+
+ [GlobalSetup]
+ public void Setup()
+ {
+ var jsonWebTokenHandler = new JsonWebTokenHandler();
+ var jwsTokenDescriptor = new SecurityTokenDescriptor
+ {
+ SigningCredentials = BenchmarkUtils.SigningCredentialsRsaSha256,
+ TokenType = JwtHeaderParameterNames.Jwk,
+ Claims = BenchmarkUtils.Claims
+ };
+
+ _encodedJWS = jsonWebTokenHandler.CreateToken(jwsTokenDescriptor);
+ }
+
+ [Benchmark]
+ public JsonWebToken ReadJWS_FromString()
+ {
+ return new JsonWebToken(_encodedJWS);
+ }
+
+ [Benchmark]
+ public JsonWebToken ReadJWS_FromMemory()
+ {
+ return new JsonWebToken(_encodedJWS.AsMemory());
+ }
+ }
+}
diff --git a/build/releaseBuild.yml b/build/releaseBuild.yml
index 9bb207b208..b020d5bce1 100644
--- a/build/releaseBuild.yml
+++ b/build/releaseBuild.yml
@@ -68,7 +68,7 @@ jobs:
inputs:
targetType: filePath
filePath: ./updateAssemblyInfo.ps1
- arguments: '-packageType $(BuildConfiguration)'
+ arguments: '-packageType $(NugetPackageType)'
- task: DotNetCoreCLI@2
displayName: Build
@@ -195,6 +195,14 @@ jobs:
BuildDropPath: '$(Build.SourcesDirectory)\src'
ManifestDirPath: '$(Build.SourcesDirectory)\artifacts'
+ - task: NuGetCommand@2
+ displayName: 'Upload NuGet Package to VSTS NuGet'
+ inputs:
+ command: push
+ packagesToPush: '$(Build.Repository.LocalPath)\artifacts\*.nupkg'
+ publishVstsFeed: '46419298-b96c-437f-bd4c-12c8df7f868d'
+ allowPackageConflicts: true
+
- task: PublishBuildArtifacts@1
displayName: 'Publish NuGet Package Artifact'
inputs:
diff --git a/buildConfiguration.xml b/buildConfiguration.xml
index 213a1a2a58..9f73fc9582 100644
--- a/buildConfiguration.xml
+++ b/buildConfiguration.xml
@@ -2,7 +2,7 @@
x64
3.5.0-rc-1285
net461,netstandard2.0
- 7.3.1
+ 7.4.0
preview
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs
index 5279e34322..ff3ab79cb1 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.HeaderClaimSet.cs
@@ -13,12 +13,17 @@ public partial class JsonWebToken
{
internal JsonClaimSet CreateHeaderClaimSet(byte[] bytes)
{
- return CreateHeaderClaimSet(bytes, bytes.Length);
+ return CreateHeaderClaimSet(bytes.AsSpan());
}
internal JsonClaimSet CreateHeaderClaimSet(byte[] bytes, int length)
{
- Utf8JsonReader reader = new(bytes.AsSpan().Slice(0, length));
+ return CreateHeaderClaimSet(bytes.AsSpan(0, length));
+ }
+
+ internal JsonClaimSet CreateHeaderClaimSet(ReadOnlySpan byteSpan)
+ {
+ Utf8JsonReader reader = new(byteSpan);
if (!JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.StartObject, false))
throw LogHelper.LogExceptionMessage(
new JsonException(
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs
index 24e19225c8..aad919b03a 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/Json/JsonWebToken.PayloadClaimSet.cs
@@ -14,7 +14,12 @@ public partial class JsonWebToken
{
internal JsonClaimSet CreatePayloadClaimSet(byte[] bytes, int length)
{
- Utf8JsonReader reader = new(bytes.AsSpan().Slice(0, length));
+ return CreatePayloadClaimSet(bytes.AsSpan(0, length));
+ }
+
+ internal JsonClaimSet CreatePayloadClaimSet(ReadOnlySpan byteSpan)
+ {
+ Utf8JsonReader reader = new(byteSpan);
if (!JsonSerializerPrimitives.IsReaderAtTokenType(ref reader, JsonTokenType.StartObject, false))
throw LogHelper.LogExceptionMessage(
new JsonException(
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs
index 58652ff45b..a8c1c38376 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebToken.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
@@ -27,9 +28,11 @@ public partial class JsonWebToken : SecurityToken
private string _encodedHeader;
private string _encodedPayload;
private string _encodedSignature;
+ private string _encodedToken;
private string _encryptedKey;
private string _initializationVector;
private List _audiences;
+ private readonly ReadOnlyMemory _encodedTokenMemory;
#region properties relating to the header
// when constructing a JWT, these properties, when found, will be set
@@ -78,7 +81,33 @@ public JsonWebToken(string jwtEncodedString)
if (string.IsNullOrEmpty(jwtEncodedString))
throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(jwtEncodedString)));
- ReadToken(jwtEncodedString);
+ ReadToken(jwtEncodedString.AsMemory());
+
+ _encodedToken = jwtEncodedString;
+ }
+
+ ///
+ /// Initializes a new instance of from a ReadOnlyMemory{char} in JWS or JWE Compact serialized format.
+ ///
+ /// A ReadOnlyMemory{char} containing the JSON Web Token serialized in JWS or JWE Compact format.
+ /// Thrown when is empty.
+ /// Thrown when does not represent a valid JWS or JWE Compact serialization format.
+ ///
+ /// See: https://datatracker.ietf.org/doc/html/rfc7519 (JWT)
+ /// See: https://datatracker.ietf.org/doc/html/rfc7515 (JWS)
+ /// See: https://datatracker.ietf.org/doc/html/rfc7516 (JWE)
+ ///
+ /// The contents of the returned have not been validated; the JSON Web Token is simply decoded. Validation can be performed using the methods in .
+ ///
+ ///
+ public JsonWebToken(ReadOnlyMemory encodedTokenMemory)
+ {
+ if (encodedTokenMemory.IsEmpty)
+ throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(encodedTokenMemory)));
+
+ ReadToken(encodedTokenMemory);
+
+ _encodedTokenMemory = encodedTokenMemory;
}
///
@@ -108,7 +137,9 @@ public JsonWebToken(string header, string payload)
var encodedPayload = Base64UrlEncoder.Encode(payload);
var encodedToken = encodedHeader + "." + encodedPayload + ".";
- ReadToken(encodedToken);
+ ReadToken(encodedToken.AsMemory());
+
+ _encodedToken = encodedToken;
}
internal string ActualIssuer { get; set; }
@@ -192,10 +223,10 @@ public string EncodedHeader
// TODO - need to account for JWE
if (_encodedHeader == null)
{
- if (EncodedToken != null)
- _encodedHeader = EncodedToken.Substring(0, Dot1);
+ if (!_encodedTokenMemory.IsEmpty)
+ _encodedHeader = _encodedTokenMemory.Span.Slice(0, Dot1).ToString();
else
- _encodedHeader = string.Empty;
+ _encodedHeader = (_encodedToken is not null) ? _encodedToken.Substring(0, Dot1) : string.Empty;
}
return _encodedHeader;
@@ -237,10 +268,17 @@ public string EncodedPayload
{
if (_encodedPayload == null)
{
- if (EncodedToken != null)
- _encodedPayload = IsEncrypted ? string.Empty : EncodedToken.Substring(Dot1 + 1, Dot2 - Dot1 - 1);
+ if (!_encodedTokenMemory.IsEmpty)
+ {
+ _encodedPayload = IsEncrypted ? string.Empty : _encodedTokenMemory.Span.Slice(Dot1 + 1, Dot2 - Dot1 - 1).ToString();
+ }
else
- _encodedPayload = string.Empty;
+ {
+ if (_encodedToken is not null)
+ _encodedPayload = IsEncrypted ? string.Empty : _encodedToken.Substring(Dot1 + 1, Dot2 - Dot1 - 1);
+ else
+ _encodedPayload = string.Empty;
+ }
}
return _encodedPayload;
@@ -260,10 +298,17 @@ public string EncodedSignature
{
if (_encodedSignature == null)
{
- if (EncodedToken != null)
- _encodedSignature = IsEncrypted ? string.Empty : EncodedToken.Substring(Dot2 + 1, EncodedToken.Length - Dot2 - 1);
+ if (!_encodedTokenMemory.IsEmpty)
+ {
+ _encodedSignature = IsEncrypted ? string.Empty : _encodedTokenMemory.Span.Slice(Dot2 + 1, _encodedTokenMemory.Length - Dot2 - 1).ToString();
+ }
else
- _encodedSignature = string.Empty;
+ {
+ if (_encodedToken is not null)
+ _encodedSignature = IsEncrypted ? string.Empty : _encodedToken.Substring(Dot2 + 1, _encodedToken.Length - Dot2 - 1);
+ else
+ _encodedSignature = string.Empty;
+ }
}
return _encodedSignature;
@@ -276,7 +321,16 @@ public string EncodedSignature
///
/// The original Base64UrlEncoded of the JWT.
///
- public string EncodedToken { get; private set; }
+ public string EncodedToken
+ {
+ get
+ {
+ if (_encodedToken is null && !_encodedTokenMemory.IsEmpty)
+ _encodedToken = _encodedTokenMemory.ToString();
+
+ return _encodedToken;
+ }
+ }
internal JsonClaimSet Header { get; set; }
@@ -347,27 +401,27 @@ public string InitializationVector
internal int NumberOfDots { get; set; }
///
- /// Converts a string into an instance of .
+ /// Converts a span into an instance of .
///
- /// A 'JSON Web Token' (JWT) in JWS or JWE Compact Serialization Format.
- /// if is malformed, a valid JWT should have either 2 dots (JWS) or 4 dots (JWE).
- /// if does not have an non-empty authentication tag after the 4th dot for a JWE.
- /// if has more than 4 dots.
- internal void ReadToken(string encodedJson)
+ /// A span representing a 'JSON Web Token' (JWT) in JWS or JWE Compact Serialization Format.
+ /// if is malformed, a valid JWT should have either 2 dots (JWS) or 4 dots (JWE).
+ /// if does not have a non-empty authentication tag after the 4th dot for a JWE.
+ /// if has more than 4 dots.
+ internal void ReadToken(ReadOnlyMemory encodedTokenMemory)
{
- // JWT must have 2 dots
- Dot1 = encodedJson.IndexOf('.');
- if (Dot1 == -1 || Dot1 == encodedJson.Length - 1)
+ // JWT must have 2 dots for JWS or 4 dots for JWE (a.b.c.d.e)
+ ReadOnlySpan encodedTokenSpan = encodedTokenMemory.Span;
+
+ Dot1 = encodedTokenSpan.IndexOf('.');
+ if (Dot1 == -1 || Dot1 == encodedTokenSpan.Length - 1)
throw LogHelper.LogExceptionMessage(new SecurityTokenMalformedException(LogMessages.IDX14100));
- Dot2 = encodedJson.IndexOf('.', Dot1 + 1);
+ Dot2 = encodedTokenSpan.Slice(Dot1 + 1).IndexOf('.');
if (Dot2 == -1)
throw LogHelper.LogExceptionMessage(new SecurityTokenMalformedException(LogMessages.IDX14120));
- if (Dot2 == encodedJson.Length - 1)
- Dot3 = -1;
- else
- Dot3 = encodedJson.IndexOf('.', Dot2 + 1);
+ Dot2 = Dot1 + Dot2 + 1;
+ Dot3 = (Dot2 == encodedTokenSpan.Length - 1) ? -1 : encodedTokenSpan.Slice(Dot2 + 1).IndexOf('.');
if (Dot3 == -1)
{
@@ -375,28 +429,28 @@ internal void ReadToken(string encodedJson)
// JWS: https://www.rfc-editor.org/rfc/rfc7515
// Format: https://www.rfc-editor.org/rfc/rfc7515#page-7
- IsSigned = !(Dot2 + 1 == encodedJson.Length);
+ IsSigned = !(Dot2 + 1 == encodedTokenSpan.Length);
try
{
- Header = CreateClaimSet(encodedJson, 0, Dot1, CreateHeaderClaimSet);
+ Header = CreateClaimSet(encodedTokenSpan, 0, Dot1, createHeaderClaimSet: true);
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(
LogMessages.IDX14102,
- LogHelper.MarkAsUnsafeSecurityArtifact(encodedJson.Substring(0, Dot1), t => t.ToString())),
+ LogHelper.MarkAsUnsafeSecurityArtifact(encodedTokenSpan.Slice(0, Dot1).ToString(), t => t.ToString())), // TODO: Add an overload to LogHelper.MarkAsUnsafeSecurityArtifact that accepts span?
ex));
}
try
{
- Payload = CreateClaimSet(encodedJson, Dot1 + 1, Dot2 - Dot1 - 1, CreatePayloadClaimSet);
+ Payload = CreateClaimSet(encodedTokenSpan, Dot1 + 1, Dot2 - Dot1 - 1, createHeaderClaimSet: false);
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(
LogMessages.IDX14101,
- LogHelper.MarkAsUnsafeSecurityArtifact(encodedJson.Substring(Dot1 + 1, Dot2 - Dot1 - 1), t => t.ToString())),
+ LogHelper.MarkAsUnsafeSecurityArtifact(encodedTokenSpan.Slice(Dot1 + 1, Dot2 - Dot1 - 1).ToString(), t => t.ToString())),
ex));
}
}
@@ -407,115 +461,123 @@ internal void ReadToken(string encodedJson)
// empty payload for JWE's {encrypted tokens}.
Payload = new JsonClaimSet();
- if (Dot3 == encodedJson.Length)
+ if (Dot3 == encodedTokenSpan.Length) // TODO: Should this be encodedJsonSpan.Length - 1?
throw LogHelper.LogExceptionMessage(new ArgumentException(LogMessages.IDX14121));
- Dot4 = encodedJson.IndexOf('.', Dot3 + 1);
+ Dot3 = Dot2 + Dot3 + 1;
- // JWE needs to have 4 dots
+ Dot4 = encodedTokenSpan.Slice(Dot3 + 1).IndexOf('.');
if (Dot4 == -1)
throw LogHelper.LogExceptionMessage(new SecurityTokenMalformedException(LogMessages.IDX14121));
-
- // too many dots...
- if (encodedJson.IndexOf('.', Dot4 + 1) != -1)
- throw LogHelper.LogExceptionMessage(new SecurityTokenMalformedException(LogMessages.IDX14122));
+
+ Dot4 = Dot3 + Dot4 + 1;
// must have something after 4th dot
- if (Dot4 == encodedJson.Length - 1)
+ if (Dot4 == encodedTokenSpan.Length - 1)
throw LogHelper.LogExceptionMessage(new SecurityTokenMalformedException(LogMessages.IDX14310));
- // right number of dots for JWE
- ReadOnlyMemory hChars = encodedJson.AsMemory(0, Dot1);
+ if (encodedTokenSpan.Slice(Dot4 + 1).IndexOf('.') != -1)
+ throw LogHelper.LogExceptionMessage(new SecurityTokenMalformedException(LogMessages.IDX14122));
- // header cannot be empty
- if (hChars.IsEmpty)
+ ReadOnlySpan headerSpan = encodedTokenSpan.Slice(0, Dot1);
+ if (headerSpan.IsEmpty)
throw LogHelper.LogExceptionMessage(new ArgumentException(LogMessages.IDX14307));
- byte[] headerAsciiBytes = new byte[hChars.Length];
+ // right number of dots for JWE (4)
+ byte[] headerAsciiBytes = new byte[headerSpan.Length];
#if NET6_0_OR_GREATER
- Encoding.ASCII.GetBytes(hChars.Span, headerAsciiBytes);
+ Encoding.ASCII.GetBytes(headerSpan, headerAsciiBytes);
#else
unsafe
{
- fixed (char* hCharsPtr = hChars.Span)
+ fixed (char* hCharsPtr = headerSpan)
fixed (byte* headerAsciiBytesPtr = headerAsciiBytes)
{
- Encoding.ASCII.GetBytes(hCharsPtr, hChars.Length, headerAsciiBytesPtr, headerAsciiBytes.Length);
+ Encoding.ASCII.GetBytes(hCharsPtr, headerSpan.Length, headerAsciiBytesPtr, headerAsciiBytes.Length);
}
}
#endif
+
HeaderAsciiBytes = headerAsciiBytes;
try
{
- Header = CreateHeaderClaimSet(Base64UrlEncoder.UnsafeDecode(hChars));
+ Header = CreateHeaderClaimSet(Base64UrlEncoder.UnsafeDecode(headerSpan).AsSpan());
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new ArgumentException(LogHelper.FormatInvariant(
LogMessages.IDX14102,
- LogHelper.MarkAsUnsafeSecurityArtifact(encodedJson.Substring(0, Dot1), t => t.ToString())),
+ LogHelper.MarkAsUnsafeSecurityArtifact(headerSpan.ToString(), t => t.ToString())),
ex));
}
- // dir does not have any key bytes
- ReadOnlyMemory encryptedKeyBytes = encodedJson.AsMemory(Dot1 + 1, Dot2 - Dot1 - 1);
+ // delegating retrieving encrypted Key to the getter on EncryptedKey
+ ReadOnlySpan encryptedKeyBytes = encodedTokenSpan.Slice(Dot1 + 1, Dot2 - Dot1 - 1);
if (!encryptedKeyBytes.IsEmpty)
{
EncryptedKeyBytes = Base64UrlEncoder.UnsafeDecode(encryptedKeyBytes);
- _encryptedKey = encodedJson.Substring(Dot1 + 1, Dot2 - Dot1 - 1);
+ _encryptedKey = encodedTokenSpan.Slice(Dot1 + 1, Dot2 - Dot1 - 1).ToString();
}
else
{
_encryptedKey = string.Empty;
}
- ReadOnlyMemory initializationVectorChars = encodedJson.AsMemory(Dot2 + 1, Dot3 - Dot2 - 1);
- if (initializationVectorChars.IsEmpty)
+ ReadOnlySpan initializationVectorSpan = encodedTokenSpan.Slice(Dot2 + 1, Dot3 - Dot2 - 1);
+ if (initializationVectorSpan.IsEmpty)
throw LogHelper.LogExceptionMessage(new ArgumentException(LogMessages.IDX14308));
try
{
- InitializationVectorBytes = Base64UrlEncoder.UnsafeDecode(initializationVectorChars);
+ InitializationVectorBytes = Base64UrlEncoder.UnsafeDecode(initializationVectorSpan);
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new ArgumentException(LogMessages.IDX14309, ex));
}
- ReadOnlyMemory authTagChars = encodedJson.AsMemory(Dot4 + 1);
- if (authTagChars.IsEmpty)
+ ReadOnlySpan authTagSpan = encodedTokenSpan.Slice(Dot4 + 1);
+ if (authTagSpan.IsEmpty)
throw LogHelper.LogExceptionMessage(new ArgumentException(LogMessages.IDX14310));
try
{
- AuthenticationTagBytes = Base64UrlEncoder.UnsafeDecode(authTagChars);
+ AuthenticationTagBytes = Base64UrlEncoder.UnsafeDecode(authTagSpan);
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new ArgumentException(LogMessages.IDX14311, ex));
}
- ReadOnlyMemory cipherTextBytes = encodedJson.AsMemory(Dot3 + 1, Dot4 - Dot3 - 1);
- if (cipherTextBytes.IsEmpty)
+ ReadOnlySpan cipherTextSpan = encodedTokenSpan.Slice(Dot3 + 1, Dot4 - Dot3 - 1);
+ if (cipherTextSpan.IsEmpty)
throw LogHelper.LogExceptionMessage(new ArgumentException(LogMessages.IDX14306));
try
{
- CipherTextBytes = Base64UrlEncoder.UnsafeDecode(cipherTextBytes);
+ CipherTextBytes = Base64UrlEncoder.UnsafeDecode(cipherTextSpan);
}
catch (Exception ex)
{
throw LogHelper.LogExceptionMessage(new ArgumentException(LogMessages.IDX14312, ex));
}
}
-
- EncodedToken = encodedJson;
}
- internal static JsonClaimSet CreateClaimSet(string rawString, int startIndex, int length, Func action)
+ internal JsonClaimSet CreateClaimSet(ReadOnlySpan strSpan, int startIndex, int length, bool createHeaderClaimSet)
{
- return Base64UrlEncoding.Decode(rawString, startIndex, length, action);
+ int outputSize = Base64UrlEncoding.ValidateAndGetOutputSize(strSpan, startIndex, length);
+ byte[] output = ArrayPool.Shared.Rent(outputSize);
+ try
+ {
+ Base64UrlEncoding.Decode(strSpan, startIndex, length, output);
+ return createHeaderClaimSet ? CreateHeaderClaimSet(output.AsSpan()) : CreatePayloadClaimSet(output.AsSpan());
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(output);
+ }
}
///
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
index c5d42bd84f..3d7882e8d6 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/JsonWebTokenHandler.cs
@@ -17,7 +17,7 @@
namespace Microsoft.IdentityModel.JsonWebTokens
{
///
- /// A designed for creating and validating Json Web Tokens.
+ /// A designed for creating and validating Json Web Tokens.
/// See: https://datatracker.ietf.org/doc/html/rfc7519 and http://www.rfc-editor.org/info/rfc7515.
///
public partial class JsonWebTokenHandler : TokenHandler
@@ -38,7 +38,7 @@ public partial class JsonWebTokenHandler : TokenHandler
public static bool DefaultMapInboundClaims = false;
///
- /// Gets the Base64Url encoded string representation of the following JWT header:
+ /// Gets the Base64Url encoded string representation of the following JWT header:
/// { , }.
///
/// The Base64Url encoded string representation of the unsigned JWT header.
@@ -85,7 +85,7 @@ public static string ShortClaimTypeProperty
}
///
- /// Gets or sets the property which is used when determining whether or not to map claim types that are extracted when validating a .
+ /// Gets or sets the property which is used when determining whether or not to map claim types that are extracted when validating a .
/// If this is set to true, the is set to the JSON claim 'name' after translating using this mapping. Otherwise, no mapping occurs.
/// The default value is false.
///
@@ -104,7 +104,7 @@ public bool MapInboundClaims
}
///
- /// Gets or sets the which is used when setting the for claims in the extracted when validating a .
+ /// Gets or sets the which is used when setting the for claims in the extracted when validating a .
/// The is set to the JSON claim 'name' after translating using this mapping.
/// The default value is ClaimTypeMapping.InboundClaimTypeMap.
///
@@ -331,7 +331,7 @@ private ClaimsIdentity CreateClaimsIdentityPrivate(JsonWebToken jwtToken, TokenV
}
///
- /// Decrypts a JWE and returns the clear text
+ /// Decrypts a JWE and returns the clear text
///
/// the JWE that contains the cypher text.
/// contains crypto material.
diff --git a/src/Microsoft.IdentityModel.JsonWebTokens/LogMessages.cs b/src/Microsoft.IdentityModel.JsonWebTokens/LogMessages.cs
index b7dd1b2519..12121de2f1 100644
--- a/src/Microsoft.IdentityModel.JsonWebTokens/LogMessages.cs
+++ b/src/Microsoft.IdentityModel.JsonWebTokens/LogMessages.cs
@@ -11,8 +11,6 @@ namespace Microsoft.IdentityModel.JsonWebTokens
///
internal static class LogMessages
{
- #pragma warning disable 1591
-
// signature creation / validation
internal const string IDX14000 = "IDX14000: Signature validation of this JWT is not supported for: Algorithm: '{0}', SecurityKey: '{1}'.";
@@ -32,14 +30,13 @@ internal static class LogMessages
internal const string IDX14116 = "IDX14116: '{0}' cannot contain the following claims: '{1}'. These values are added by default (if necessary) during security token creation.";
// number of sections 'dots' is not correct
internal const string IDX14120 = "IDX14120: JWT is not well formed, there is only one dot (.).\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.";
- internal const string IDX14121 = "IDX14121: JWT is not a well formed JWE, there are there must be four dots (.).\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.";
+ internal const string IDX14121 = "IDX14121: JWT is not a well formed JWE, there must be four dots (.).\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.";
internal const string IDX14122 = "IDX14122: JWT is not a well formed JWE, there are more than four dots (.) a JWE can have at most 4 dots.\nThe token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.";
// logging
internal const string IDX14200 = "IDX14200: Creating raw signature using the signature credentials.";
internal const string IDX14201 = "IDX14201: Creating raw signature using the signature credentials. Caching SignatureProvider: '{0}'.";
-
// parsing
//internal const string IDX14300 = "IDX14300: Could not parse '{0}' : '{1}' as a '{2}'.";
//internal const string IDX14301 = "IDX14301: Unable to parse the header into a JSON object. \nHeader: '{0}'.";
@@ -54,7 +51,5 @@ internal static class LogMessages
internal const string IDX14310 = "IDX14310: JWE authentication tag is missing.";
internal const string IDX14311 = "IDX14311: Unable to decode the authentication tag as a Base64Url encoded string.";
internal const string IDX14312 = "IDX14312: Unable to decode the cipher text as a Base64Url encoded string.";
-
- #pragma warning restore 1591
}
}
diff --git a/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoder.cs b/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoder.cs
index db6d1bc0d2..8a11a07c3c 100644
--- a/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoder.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoder.cs
@@ -173,20 +173,20 @@ public static int Encode(ReadOnlySpan inArray, Span output)
public static byte[] DecodeBytes(string str)
{
_ = str ?? throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(str)));
- return UnsafeDecode(str.AsMemory());
+ return UnsafeDecode(str.AsSpan());
}
#if NET6_0_OR_GREATER
[SkipLocalsInit]
#endif
- internal static unsafe byte[] UnsafeDecode(ReadOnlyMemory str)
+ internal static unsafe byte[] UnsafeDecode(ReadOnlySpan strSpan)
{
- int mod = str.Length % 4;
+ int mod = strSpan.Length % 4;
if (mod == 1)
- throw LogHelper.LogExceptionMessage(new FormatException(LogHelper.FormatInvariant(LogMessages.IDX10400, str.ToString())));
+ throw LogHelper.LogExceptionMessage(new FormatException(LogHelper.FormatInvariant(LogMessages.IDX10400, strSpan.ToString())));
- bool needReplace = str.Span.IndexOfAny(base64UrlCharacter62, base64UrlCharacter63) >= 0;
- int decodedLength = str.Length + (4 - mod) % 4;
+ bool needReplace = strSpan.IndexOfAny(base64UrlCharacter62, base64UrlCharacter63) >= 0;
+ int decodedLength = strSpan.Length + (4 - mod) % 4;
#if NET6_0_OR_GREATER
// If the incoming chars don't contain any of the base64url characters that need to be replaced,
@@ -198,7 +198,7 @@ internal static unsafe byte[] UnsafeDecode(ReadOnlyMemory str)
const int StackAllocThreshold = 512;
char[] arrayPoolChars = null;
scoped Span charsSpan = default;
- scoped ReadOnlySpan source = str.Span;
+ scoped ReadOnlySpan source = strSpan;
if (needReplace || decodedLength != source.Length)
{
@@ -256,7 +256,6 @@ internal static unsafe byte[] UnsafeDecode(ReadOnlyMemory str)
#else
if (needReplace)
{
- ReadOnlySpan strSpan = str.Span;
string decodedString = new(char.MinValue, decodedLength);
fixed (char* dest = decodedString)
{
@@ -279,30 +278,21 @@ internal static unsafe byte[] UnsafeDecode(ReadOnlyMemory str)
}
else
{
- if (decodedLength == str.Length)
+ if (decodedLength == strSpan.Length)
{
- if (MemoryMarshal.TryGetArray(str, out ArraySegment segment))
- {
- return Convert.FromBase64CharArray(segment.Array, segment.Offset, segment.Count);
- }
- else
- {
- bool gotString = MemoryMarshal.TryGetString(str, out string text, out int start, out int length);
- Debug.Assert(gotString, "Expected ReadOnlyMemory to wrap either array or string");
- return Convert.FromBase64String(text.Substring(start, length));
- }
+ return Convert.FromBase64CharArray(strSpan.ToArray(), 0, strSpan.Length);
}
else
{
string decodedString = new(char.MinValue, decodedLength);
- fixed (char* src = str.Span)
+ fixed (char* src = strSpan)
fixed (char* dest = decodedString)
{
- Buffer.MemoryCopy(src, dest, str.Length * 2, str.Length * 2);
+ Buffer.MemoryCopy(src, dest, strSpan.Length * 2, strSpan.Length * 2);
- dest[str.Length] = base64PadCharacter;
- if (str.Length + 2 == decodedLength)
- dest[str.Length + 1] = base64PadCharacter;
+ dest[strSpan.Length] = base64PadCharacter;
+ if (strSpan.Length + 2 == decodedLength)
+ dest[strSpan.Length + 1] = base64PadCharacter;
}
return Convert.FromBase64String(decodedString);
diff --git a/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoding.cs b/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoding.cs
index 4c5b6effe6..1cdbd0809f 100644
--- a/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoding.cs
+++ b/src/Microsoft.IdentityModel.Tokens/Base64UrlEncoding.cs
@@ -57,9 +57,9 @@ public static byte[] Decode(string input, int offset, int length)
{
_ = input ?? throw LogHelper.LogArgumentNullException(nameof(input));
- int outputsize = ValidateAndGetOutputSize(input, offset, length);
+ int outputsize = ValidateAndGetOutputSize(input.AsSpan(), offset, length);
byte[] output = new byte[outputsize];
- Decode(input, offset, length, output);
+ Decode(input.AsSpan(), offset, length, output);
return output;
}
@@ -77,16 +77,16 @@ public static byte[] Decode(string input, int offset, int length)
///
/// The buffer for the decode operation uses shared memory pool to avoid allocations.
/// The length of the rented array of bytes may be larger than the decoded bytes, therefore the action needs to know the actual length to use.
- /// The result of is passed to the action.
+ /// The result of is passed to the action.
///
public static T Decode(string input, int offset, int length, TX argx, Func action)
{
_ = action ?? throw new ArgumentNullException(nameof(action));
- int outputsize = ValidateAndGetOutputSize(input, offset, length);
+ int outputsize = ValidateAndGetOutputSize(input.AsSpan(), offset, length);
byte[] output = ArrayPool.Shared.Rent(outputsize);
try
{
- Decode(input, offset, length, output);
+ Decode(input.AsSpan(), offset, length, output);
return action(output, outputsize, argx);
}
finally
@@ -107,7 +107,7 @@ public static T Decode(string input, int offset, int length, TX argx, Fun
///
/// The buffer for the decode operation uses shared memory pool to avoid allocations.
/// The length of the rented array of bytes may be larger than the decoded bytes, therefore the action needs to know the actual length to use.
- /// The result of is passed to the action.
+ /// The result of is passed to the action.
///
public static T Decode(string input, int offset, int length, Func action)
{
@@ -117,7 +117,7 @@ public static T Decode(string input, int offset, int length, Func.Shared.Rent(outputsize);
try
{
- Decode(input, offset, length, output);
+ Decode(input.AsSpan(), offset, length, output);
return action(output, outputsize);
}
finally
@@ -144,7 +144,7 @@ public static T Decode(string input, int offset, int length, Func
/// The buffer for the decode operation uses shared memory pool to avoid allocations.
/// The length of the rented array of bytes may be larger than the decoded bytes, therefore the action needs to know the actual length to use.
- /// The result of is passed to the action.
+ /// The result of is passed to the action.
///
public static T Decode(
string input,
@@ -157,11 +157,11 @@ public static T Decode(
{
_ = action ?? throw LogHelper.LogArgumentNullException(nameof(action));
- int outputsize = ValidateAndGetOutputSize(input, offset, length);
+ int outputsize = ValidateAndGetOutputSize(input.AsSpan(), offset, length);
byte[] output = ArrayPool.Shared.Rent(outputsize);
try
{
- Decode(input, offset, length, output);
+ Decode(input.AsSpan(), offset, length, output);
return action(output, outputsize, argx, argy, argz);
}
finally
@@ -173,9 +173,9 @@ public static T Decode(
///
/// Decodes a Base64UrlEncoded string into a byte array.
///
- /// String to decode.
+ /// String represented as a span to decode.
/// Index of char in to start decode operation.
- /// Number of chars in to decode.
+ /// Number of chars beginning from to decode.
/// byte array to place results.
///
/// Changes from Base64UrlEncoder implementation
@@ -183,7 +183,7 @@ public static T Decode(
/// 2. '+' and '-' are treated the same.
/// 3. '/' and '_' are treated the same.
///
- private static void Decode(string input, int offset, int length, byte[] output)
+ internal static void Decode(ReadOnlySpan input, int offset, int length, byte[] output)
{
int outputpos = 0;
uint curblock = 0x000000FFu;
@@ -220,7 +220,7 @@ private static void Decode(string input, int offset, int length, byte[] output)
LogHelper.FormatInvariant(
LogMessages.IDX10820,
LogHelper.MarkAsNonPII(cur),
- input)));
+ input.ToString())));
}
curblock = (curblock << 6) | cur;
@@ -254,7 +254,7 @@ private static void Decode(string input, int offset, int length, byte[] output)
else
{
throw LogHelper.LogExceptionMessage(new ArgumentException(
- LogHelper.FormatInvariant(LogMessages.IDX10821, input)));
+ LogHelper.FormatInvariant(LogMessages.IDX10821, input.ToString())));
}
}
}
@@ -335,8 +335,21 @@ public static string Encode(byte[] input, int offset, int length)
private static int ValidateAndGetOutputSize(string inputString, int offset, int length)
{
_ = inputString ?? throw LogHelper.LogArgumentNullException(nameof(inputString));
- if (inputString.Length == 0)
- return 0;
+
+ return ValidateAndGetOutputSize(inputString.AsSpan(), offset, length);
+ }
+
+ ///
+ /// Validates the input span for decode operation.
+ ///
+ /// String represented by a span to validate.
+ /// Index of char in to start decode operation.
+ /// Number of chars in to decode, starting from offset.
+ /// Size of the decoded bytes arrays.
+ internal static int ValidateAndGetOutputSize(ReadOnlySpan strSpan, int offset, int length)
+ {
+ if (strSpan.IsEmpty)
+ throw LogHelper.LogArgumentNullException(nameof(strSpan));
if (length == 0)
return 0;
@@ -355,37 +368,37 @@ private static int ValidateAndGetOutputSize(string inputString, int offset, int
LogHelper.MarkAsNonPII(nameof(length)),
LogHelper.MarkAsNonPII(length))));
- if (length + offset > inputString.Length)
+ if (length + offset > strSpan.Length)
throw LogHelper.LogExceptionMessage(new ArgumentException(
LogHelper.FormatInvariant(
LogMessages.IDX10717,
LogHelper.MarkAsNonPII(nameof(length)),
LogHelper.MarkAsNonPII(nameof(offset)),
- LogHelper.MarkAsNonPII(nameof(inputString)),
+ LogHelper.MarkAsNonPII(nameof(strSpan)),
LogHelper.MarkAsNonPII(length),
LogHelper.MarkAsNonPII(offset),
- LogHelper.MarkAsNonPII(inputString.Length))));
+ LogHelper.MarkAsNonPII(strSpan.Length))));
if (length % 4 == 1)
- throw LogHelper.LogExceptionMessage(new FormatException(LogHelper.FormatInvariant(LogMessages.IDX10400, inputString)));
+ throw LogHelper.LogExceptionMessage(new FormatException(LogHelper.FormatInvariant(LogMessages.IDX10400, strSpan.ToString())));
int lastCharPosition = offset + length - 1;
// Compute useful length (i.e. ignore padding characters)
- if (inputString[lastCharPosition] == '=')
+ if (strSpan[lastCharPosition] == '=')
{
lastCharPosition--;
- if (inputString[lastCharPosition] == '=')
+ if (strSpan[lastCharPosition] == '=')
lastCharPosition--;
}
int effectiveLength = 1 + (lastCharPosition - offset);
- int outputsize = effectiveLength % 4;
- if (outputsize > 0)
- outputsize--;
+ int outputSize = effectiveLength % 4;
+ if (outputSize > 0)
+ outputSize--;
- outputsize += (effectiveLength / 4) * 3;
- return outputsize;
+ outputSize += (effectiveLength / 4) * 3;
+ return outputSize;
}
private static void WriteEncodedOutput(byte[] inputBytes, int offset, int length, Span output)
diff --git a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs
index 5f6a768328..7c5892ff3f 100644
--- a/src/Microsoft.IdentityModel.Tokens/LogMessages.cs
+++ b/src/Microsoft.IdentityModel.Tokens/LogMessages.cs
@@ -86,7 +86,8 @@ internal static class LogMessages
public const string IDX10503 = "IDX10503: Signature validation failed. The token's kid is: '{0}', but did not match any keys in TokenValidationParameters or Configuration. Keys tried: '{1}'. Number of keys in TokenValidationParameters: '{2}'. \nNumber of keys in Configuration: '{3}'. \nExceptions caught:\n '{4}'.\ntoken: '{5}'. See https://aka.ms/IDX10503 for details.";
public const string IDX10504 = "IDX10504: Unable to validate signature, token does not have a signature: '{0}'.";
public const string IDX10505 = "IDX10505: Signature validation failed. The user defined 'Delegate' specified on TokenValidationParameters returned null when validating token: '{0}'.";
- public const string IDX10506 = "IDX10506: Signature validation failed. The user defined 'Delegate' specified on TokenValidationParameters did not return a '{0}', but returned a '{1}' when validating token: '{2}'.";
+ // Provide a message more specific to JsonWebTokens while allowing people searching the ID to search solutions provided for the old message like those at https://stackoverflow.com/questions/77515249/custom-token-validator-not-working-in-net-8
+ public const string IDX10506 = "IDX10506: Signature validation failed. The user defined 'Delegate' specified on TokenValidationParameters did not return a '{0}', but returned a '{1}' when validating token: '{2}'. If you are using ASP.NET Core 8 or later, see https://learn.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/8.0/securitytoken-events for more details.";
// public const string IDX10507 = "IDX10507:";
public const string IDX10508 = "IDX10508: Signature validation failed. Signature is improperly formatted.";
public const string IDX10509 = "IDX10509: Token validation failed. The user defined 'Delegate' set on TokenValidationParameters.TokenReader did not return a '{0}', but returned a '{1}' when reading token: '{2}'.";
diff --git a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs
index 51c901c1e2..c0a2362b24 100644
--- a/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs
+++ b/test/Microsoft.IdentityModel.JsonWebTokens.Tests/JsonWebTokenTests.cs
@@ -1598,6 +1598,30 @@ public static TheoryData ParseTokenTheoryData
Token = EncodedJwts.JWEEmptyAuthenticationTag,
});
+ theoryData.Add(new JwtTheoryData(nameof(EncodedJwts.JWEInvalidHeader))
+ {
+ ExpectedException = new ExpectedException(typeof(ArgumentException), "IDX14102:", typeof(FormatException), true),
+ Token = EncodedJwts.JWEInvalidHeader,
+ });
+
+ theoryData.Add(new JwtTheoryData(nameof(EncodedJwts.JWEInvalidIV))
+ {
+ ExpectedException = new ExpectedException(typeof(ArgumentException), "IDX14309:", typeof(FormatException), true),
+ Token = EncodedJwts.JWEInvalidIV,
+ });
+
+ theoryData.Add(new JwtTheoryData(nameof(EncodedJwts.JWEInvalidCiphertext))
+ {
+ ExpectedException = new ExpectedException(typeof(ArgumentException), "IDX14312:", typeof(FormatException), true),
+ Token = EncodedJwts.JWEInvalidCiphertext,
+ });
+
+ theoryData.Add(new JwtTheoryData(nameof(EncodedJwts.JWEInvalidAuthenticationTag))
+ {
+ ExpectedException = new ExpectedException(typeof(ArgumentException), "IDX14311:", typeof(FormatException), true),
+ Token = EncodedJwts.JWEInvalidAuthenticationTag,
+ });
+
return theoryData;
}
}
@@ -1665,6 +1689,26 @@ public void DifferentCultureJsonWebToken()
Assert.Equal("12.2", numericList[0].Value);
Assert.Equal("11.1", numericList[1].Value);
}
+
+ // Test to verify equality between JsonWebTokens created from a string and an equivalent span
+ [Theory, MemberData(nameof(ParseTokenTheoryData))]
+ public void CompareJsonWebToken(JwtTheoryData theoryData)
+ {
+ var context = TestUtilities.WriteHeader($"{this}.CompareJsonWebToken", theoryData);
+ try
+ {
+ var tokenFromMemory = new JsonWebToken(theoryData.Token.AsMemory());
+ var tokenFromString = new JsonWebToken(theoryData.Token);
+
+ theoryData.ExpectedException.ProcessNoException(context);
+ IdentityComparer.AreEqual(tokenFromMemory, tokenFromString, context);
+ }
+ catch (Exception ex)
+ {
+ theoryData.ExpectedException.ProcessException(ex, context);
+ }
+ TestUtilities.AssertFailIfErrors(context);
+ }
}
public class ParseTimeValuesTheoryData : TheoryDataBase
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/Base64UrlEncodingTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/Base64UrlEncodingTests.cs
index 2d95e766c5..0457e0f755 100644
--- a/test/Microsoft.IdentityModel.Tokens.Tests/Base64UrlEncodingTests.cs
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/Base64UrlEncodingTests.cs
@@ -307,5 +307,25 @@ public Base64UrlEncoderTheoryData(string testId) : base(testId) { }
public bool EncodingOnly { get; set; } = false;
}
+
+ [Fact]
+ public void ValidateAndGetOutputSizeTests()
+ {
+ string input = string.Empty;
+ Assert.Throws(() => Base64UrlEncoding.ValidateAndGetOutputSize(input.AsSpan(), 0, 0));
+ Assert.Throws(() => Base64UrlEncoding.ValidateAndGetOutputSize("abc".AsSpan(), -1, 3));
+ Assert.Throws(() => Base64UrlEncoding.ValidateAndGetOutputSize("abc".AsSpan(), 0, -1));
+ Assert.Throws(() => Base64UrlEncoding.ValidateAndGetOutputSize("abc".AsSpan(), 0, 4));
+ Assert.Throws(() => Base64UrlEncoding.ValidateAndGetOutputSize("abcde".AsSpan(), 0, 5));
+
+ int result = Base64UrlEncoding.ValidateAndGetOutputSize("abc".AsSpan(), 0, 0);
+ Assert.Equal(0, result);
+
+ result = Base64UrlEncoding.ValidateAndGetOutputSize("abcd".AsSpan(), 0, 4);
+ Assert.Equal(3, result);
+
+ result = Base64UrlEncoding.ValidateAndGetOutputSize("abc=".AsSpan(), 0, 4);
+ Assert.Equal(2, result);
+ }
}
}
diff --git a/test/Microsoft.IdentityModel.Tokens.Tests/SignatureProviderTests.cs b/test/Microsoft.IdentityModel.Tokens.Tests/SignatureProviderTests.cs
index d415d2120e..1464e6d694 100644
--- a/test/Microsoft.IdentityModel.Tokens.Tests/SignatureProviderTests.cs
+++ b/test/Microsoft.IdentityModel.Tokens.Tests/SignatureProviderTests.cs
@@ -1229,6 +1229,7 @@ internal static void AddSignUsingSpans(byte[] bytes, SecurityKey securityKey, st
});
}
#endif
+
[Theory, MemberData(nameof(SignUsingOffsetTestCases), DisableDiscoveryEnumeration = true)]
public void SignUsingOffsetTests(SignTheoryData theoryData)
{
diff --git a/test/System.IdentityModel.Tokens.Jwt.Tests/References.cs b/test/System.IdentityModel.Tokens.Jwt.Tests/References.cs
index eb20268271..ae1099b0e7 100644
--- a/test/System.IdentityModel.Tokens.Jwt.Tests/References.cs
+++ b/test/System.IdentityModel.Tokens.Jwt.Tests/References.cs
@@ -475,7 +475,11 @@ public static class EncodedJwts
public static string JWEEmptyEncryptedKey = @"eyJhIjoiYiJ9..eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9";
public static string JWEEmptyIV = @"eyJhIjoiYiJ9.eyJhIjoiYiJ9..eyJhIjoiYiJ9.eyJhIjoiYiJ9";
public static string JWEEmptyCiphertext = @"eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9..eyJhIjoiYiJ9";
- public static string JWEEmptyAuthenticationTag = @"eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9.";
+ public static string JWEEmptyAuthenticationTag = @"eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9.";
+ public static string JWEInvalidHeader = @"e.eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9";
+ public static string JWEInvalidIV = @"eyJhIjoiYiJ9.eyJhIjoiYiJ9.e.eyJhIjoiYiJ9.eyJhIjoiYiJ9";
+ public static string JWEInvalidCiphertext = @"eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9.e.eyJhIjoiYiJ9";
+ public static string JWEInvalidAuthenticationTag = @"eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9.eyJhIjoiYiJ9.e";
public static string JwsKidNullX5t
{
diff --git a/updateAssemblyInfo.ps1 b/updateAssemblyInfo.ps1
index ca94af04ea..10a99c3f6a 100644
--- a/updateAssemblyInfo.ps1
+++ b/updateAssemblyInfo.ps1
@@ -47,7 +47,7 @@ if ( $packageType -eq "release")
}
else
{
- $versionSuffix = $nugetSuffix + "-" + $dateTimeStamp
+ $versionSuffix = $nugetSuffix + "1"
}
Write-Host "nugetSuffix: " $nugetSuffix