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