diff --git a/perf/Sandbox/StructCopyBenchmark.cs b/perf/Sandbox/StructCopyBenchmark.cs new file mode 100644 index 00000000..a362cbd0 --- /dev/null +++ b/perf/Sandbox/StructCopyBenchmark.cs @@ -0,0 +1,69 @@ +using System.Security.Cryptography; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Diagnosers; + +namespace JsonWebToken.Performance +{ + [MemoryDiagnoser] + [DisassemblyDiagnoser] + public class StructCopyBenchmark + { + [Params(true, false)] + public bool includePrivateParameters; + + private ECParameters _parameters = new ECParameters { Curve = ECCurve.NamedCurves.nistP256, D = new byte[32], Q = new ECPoint { X = new byte[32], Y = new byte[32] } }; + + [Benchmark(Baseline = true)] + public ECParameters ExportParameters_Old() + { + var parameters = new ECParameters + { + Q = _parameters.Q, + Curve = _parameters.Curve + }; + if (includePrivateParameters) + { + parameters.D = _parameters.D; + } + + return parameters; + } + + [Benchmark] + public ECParameters ExportParameters_Old2() + { + var parameters = new ECParameters + { + Q = _parameters.Q, + Curve = _parameters.Curve + }; + parameters.D = includePrivateParameters ? _parameters.D : null; + + return parameters; + } + [Benchmark] + public ECParameters ExportParameters_Brute() + { + return _parameters; + } + [Benchmark] + public ECParameters ExportParameters_Brute2() + { + var p = _parameters; + return p; + } + + [Benchmark] + public ECParameters ExportParameters_New() + { + var parameters = _parameters; + + if (!includePrivateParameters) + { + parameters.D = null; + } + + return parameters; + } + } +} diff --git a/src/JsonWebToken/Base64.cs b/src/JsonWebToken/Base64.cs index 5a1e72ae..a76b0372 100644 --- a/src/JsonWebToken/Base64.cs +++ b/src/JsonWebToken/Base64.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace JsonWebToken @@ -46,7 +47,7 @@ public static int Decode(ReadOnlySpan base64, Span data) byte[]? arrayToReturn = null; var buffer = base64.Length > Constants.MaxStackallocBytes ? (arrayToReturn = ArrayPool.Shared.Rent(base64.Length)) - : stackalloc byte[base64.Length]; + : stackalloc byte[Constants.MaxStackallocBytes]; try { int length = Utf8.GetBytes(base64, buffer); @@ -83,7 +84,7 @@ public static OperationStatus Decode(ReadOnlySpan base64, Span data, byte[]? utf8ArrayToReturn = null; Span utf8Data = base64.Length > Constants.MaxStackallocBytes ? (utf8ArrayToReturn = ArrayPool.Shared.Rent(base64.Length)) - : stackalloc byte[base64.Length]; + : stackalloc byte[Constants.MaxStackallocBytes]; try { int length = 0; @@ -118,7 +119,7 @@ public static OperationStatus Decode(ReadOnlySpan base64, Span data, } } - private static bool IsWhiteSpace(byte c) + private static bool IsWhiteSpace(byte c) => c == ' ' || (c >= '\t' && c <= '\r'); private static ReadOnlySpan WhiteSpace => new byte[] { (byte)' ', (byte)'\t', (byte)'\r', (byte)'\n', (byte)'\v', (byte)'\f' }; @@ -173,7 +174,7 @@ public static byte[] Encode(ReadOnlySpan data) int length = Utf8.GetMaxByteCount(data.Length); var utf8Data = length > Constants.MaxStackallocBytes ? (utf8ArrayToReturn = ArrayPool.Shared.Rent(length)) - : stackalloc byte[length]; + : stackalloc byte[Constants.MaxStackallocBytes]; int written = Utf8.GetBytes(data, utf8Data); return Encode(utf8Data.Slice(0, written)); diff --git a/src/JsonWebToken/Base64Url.cs b/src/JsonWebToken/Base64Url.cs index 530fa7c8..5335be1b 100644 --- a/src/JsonWebToken/Base64Url.cs +++ b/src/JsonWebToken/Base64Url.cs @@ -26,7 +26,7 @@ public static byte[] Decode(string data) { Span tmp = length > Constants.MaxStackallocBytes ? (utf8ArrayToReturn = ArrayPool.Shared.Rent(length)) - : stackalloc byte[length]; + : stackalloc byte[Constants.MaxStackallocBytes]; int written = Utf8.GetBytes(data, tmp); return Decode(tmp.Slice(0, written)); } @@ -70,7 +70,7 @@ public static int Decode(ReadOnlySpan base64Url, Span data) byte[]? arrayToReturn = null; var buffer = base64Url.Length > Constants.MaxStackallocBytes ? (arrayToReturn = ArrayPool.Shared.Rent(base64Url.Length)) - : stackalloc byte[base64Url.Length]; + : stackalloc byte[Constants.MaxStackallocBytes]; try { int length = Utf8.GetBytes(base64Url, buffer); @@ -163,7 +163,7 @@ public static byte[] Encode(ReadOnlySpan data) int length = Utf8.GetMaxByteCount(data.Length); var utf8Data = length > Constants.MaxStackallocBytes ? (utf8ArrayToReturn = ArrayPool.Shared.Rent(length)) - : stackalloc byte[length]; + : stackalloc byte[Constants.MaxStackallocBytes]; int written = Utf8.GetBytes(data, utf8Data); return Encode(utf8Data.Slice(0, written)); diff --git a/src/JsonWebToken/Cryptography/AesCbcHmacDecryptor.cs b/src/JsonWebToken/Cryptography/AesCbcHmacDecryptor.cs index 82a9b49c..812379da 100644 --- a/src/JsonWebToken/Cryptography/AesCbcHmacDecryptor.cs +++ b/src/JsonWebToken/Cryptography/AesCbcHmacDecryptor.cs @@ -74,11 +74,12 @@ private bool VerifyAuthenticationTag(ReadOnlySpan key, ReadOnlySpan { byte[]? byteArrayToReturnToPool = null; int macLength = associatedData.Length + iv.Length + ciphertext.Length + sizeof(long); - Span macBytes = macLength <= Constants.MaxStackallocBytes - ? stackalloc byte[macLength] - : (byteArrayToReturnToPool = ArrayPool.Shared.Rent(macLength)).AsSpan(0, macLength); + Span macBytes = macLength > Constants.MaxStackallocBytes + ? (byteArrayToReturnToPool = ArrayPool.Shared.Rent(macLength)) + : stackalloc byte[Constants.MaxStackallocBytes]; try { + macBytes = macBytes.Slice(0, macLength); associatedData.CopyTo(macBytes); var bytes = macBytes.Slice(associatedData.Length); iv.CopyTo(bytes); @@ -88,9 +89,11 @@ private bool VerifyAuthenticationTag(ReadOnlySpan key, ReadOnlySpan BinaryPrimitives.WriteInt64BigEndian(bytes, associatedData.Length << 3); Sha2 hashAlgorithm = _encryptionAlgorithm.SignatureAlgorithm.Sha; - Span hmacKey = stackalloc byte[hashAlgorithm.BlockSize * 2]; + Span hmacKey = stackalloc byte[Sha2.BlockSizeStackallocThreshold * 2] + .Slice(0, hashAlgorithm.BlockSize * 2); Hmac hmac = new Hmac(hashAlgorithm, key, hmacKey); - Span hash = stackalloc byte[authenticationTag.Length * 2]; + Span hash = stackalloc byte[AuthenticatedEncryptor.TagSizeStackallocThreshold] + .Slice(0, authenticationTag.Length * 2); hmac.ComputeHash(macBytes, hash); CryptographicOperations.ZeroMemory(hmacKey); diff --git a/src/JsonWebToken/Cryptography/AesCbcHmacEncryptor.cs b/src/JsonWebToken/Cryptography/AesCbcHmacEncryptor.cs index bba1e1ca..9909b954 100644 --- a/src/JsonWebToken/Cryptography/AesCbcHmacEncryptor.cs +++ b/src/JsonWebToken/Cryptography/AesCbcHmacEncryptor.cs @@ -89,9 +89,9 @@ private void ComputeAuthenticationTag(ReadOnlySpan key, ReadOnlySpan try { int macLength = associatedData.Length + iv.Length + ciphertext.Length + sizeof(long); - Span macBytes = macLength <= Constants.MaxStackallocBytes - ? stackalloc byte[macLength] - : (arrayToReturnToPool = ArrayPool.Shared.Rent(macLength)).AsSpan(0, macLength); + Span macBytes = macLength > Constants.MaxStackallocBytes + ? (arrayToReturnToPool = ArrayPool.Shared.Rent(macLength)) + : stackalloc byte[Constants.MaxStackallocBytes]; associatedData.CopyTo(macBytes); var bytes = macBytes.Slice(associatedData.Length); @@ -102,9 +102,9 @@ private void ComputeAuthenticationTag(ReadOnlySpan key, ReadOnlySpan BinaryPrimitives.WriteInt64BigEndian(bytes, associatedData.Length << 3); Sha2 hashAlgorithm = _encryptionAlgorithm.SignatureAlgorithm.Sha; - Span hmacKey = stackalloc byte[hashAlgorithm.BlockSize * 2]; - Hmac hmac = new Hmac(hashAlgorithm, key, hmacKey); - hmac.ComputeHash(macBytes, authenticationTag); + Span hmacKey = stackalloc byte[Sha2.BlockSizeStackallocThreshold * 2]; + Hmac hmac = new Hmac(hashAlgorithm, key, hmacKey.Slice(0, hashAlgorithm.BlockSize * 2)); + hmac.ComputeHash(macBytes.Slice(0, macLength), authenticationTag); authenticationTagBytesWritten = authenticationTag.Length >> 1; } finally diff --git a/src/JsonWebToken/Cryptography/AesGcmKeyUnwrapper.cs b/src/JsonWebToken/Cryptography/AesGcmKeyUnwrapper.cs index 4844a76c..2d5d8866 100644 --- a/src/JsonWebToken/Cryptography/AesGcmKeyUnwrapper.cs +++ b/src/JsonWebToken/Cryptography/AesGcmKeyUnwrapper.cs @@ -10,6 +10,9 @@ namespace JsonWebToken.Cryptography { internal sealed class AesGcmKeyUnwrapper : KeyUnwrapper { + private const int IVSize = 12; + private const int TagSize = 16; + private readonly SymmetricJwk _key; public AesGcmKeyUnwrapper(SymmetricJwk key, EncryptionAlgorithm encryptionAlgorithm, KeyManagementAlgorithm algorithm) @@ -38,9 +41,9 @@ public override bool TryUnwrapKey(ReadOnlySpan keyBytes, Span destin } var rawIV = encodedIV.GetRawValue(); - Span nonce = stackalloc byte[Base64Url.GetArraySizeRequiredToDecode(rawIV.Length)]; + Span nonce = stackalloc byte[IVSize]; var rawTag = encodedTag.GetRawValue(); - Span tag = stackalloc byte[Base64Url.GetArraySizeRequiredToDecode(rawTag.Length)]; + Span tag = stackalloc byte[TagSize]; try { Base64Url.Decode(rawIV.Span, nonce); diff --git a/src/JsonWebToken/Cryptography/AesKeyUnwrapper.cs b/src/JsonWebToken/Cryptography/AesKeyUnwrapper.cs index abf72a81..425c63de 100644 --- a/src/JsonWebToken/Cryptography/AesKeyUnwrapper.cs +++ b/src/JsonWebToken/Cryptography/AesKeyUnwrapper.cs @@ -12,6 +12,7 @@ namespace JsonWebToken.Cryptography internal sealed class AesKeyUnwrapper : KeyUnwrapper { private const int BlockSizeInBytes = 8; + private const int KeyWrappedSizeThreshold = 64; // The default initialization vector from RFC3394 private const ulong _defaultIV = 0XA6A6A6A6A6A6A6A6; @@ -85,7 +86,7 @@ public override bool TryUnwrapKey(ReadOnlySpan key, Span destination int n = key.Length - BlockSizeInBytes; // The set of input blocks - Span r = stackalloc byte[n]; + Span r = stackalloc byte[KeyWrappedSizeThreshold].Slice(0, n); r.Clear(); ref byte rRef = ref MemoryMarshal.GetReference(r); Unsafe.CopyBlockUnaligned(ref rRef, ref Unsafe.AddByteOffset(ref input, (IntPtr)8), (uint)n); diff --git a/src/JsonWebToken/Cryptography/AesKeyWrapper.cs b/src/JsonWebToken/Cryptography/AesKeyWrapper.cs index 58f22fc7..6dfa871b 100644 --- a/src/JsonWebToken/Cryptography/AesKeyWrapper.cs +++ b/src/JsonWebToken/Cryptography/AesKeyWrapper.cs @@ -12,6 +12,7 @@ namespace JsonWebToken.Cryptography internal sealed class AesKeyWrapper : KeyWrapper { private const int BlockSizeInBytes = 8; + private const int KeyWrappedSizeThreshold = 64; // The default initialization vector from RFC3394 private const ulong _defaultIV = 0XA6A6A6A6A6A6A6A6; @@ -84,7 +85,7 @@ public override SymmetricJwk WrapKey(Jwk? staticKey, JwtHeader header, Span r = stackalloc byte[n]; + Span r = stackalloc byte[KeyWrappedSizeThreshold].Slice(0, n); ref byte rRef = ref MemoryMarshal.GetReference(r); Unsafe.CopyBlockUnaligned(ref rRef, ref input, (uint)n); TryWrapKey(ref a, n, ref rRef); diff --git a/src/JsonWebToken/Cryptography/AuthenticatedEncryptor.cs b/src/JsonWebToken/Cryptography/AuthenticatedEncryptor.cs index 0d0700d0..693b5501 100644 --- a/src/JsonWebToken/Cryptography/AuthenticatedEncryptor.cs +++ b/src/JsonWebToken/Cryptography/AuthenticatedEncryptor.cs @@ -30,5 +30,9 @@ public abstract class AuthenticatedEncryptor /// Gets the required size of the base64-URL nonce. public abstract int GetBase64NonceSize(); + + internal const int TagSizeStackallocThreshold = 64; + + internal const int NonceSizeStackallocThreshold = 16; } } \ No newline at end of file diff --git a/src/JsonWebToken/Cryptography/EcdsaSignatureVerifier.cs b/src/JsonWebToken/Cryptography/EcdsaSignatureVerifier.cs index 9f9868b1..c0ee9c6b 100644 --- a/src/JsonWebToken/Cryptography/EcdsaSignatureVerifier.cs +++ b/src/JsonWebToken/Cryptography/EcdsaSignatureVerifier.cs @@ -50,7 +50,7 @@ public override bool Verify(ReadOnlySpan data, ReadOnlySpan signatur var ecdsa = _ecdsaPool.Get(); #if SUPPORT_SPAN_CRYPTO - Span hash = stackalloc byte[_sha.HashSize]; + Span hash = stackalloc byte[Sha2.BlockSizeStackallocThreshold].Slice(0, _sha.HashSize); _sha.ComputeHash(data, hash); return ecdsa.VerifyHash(hash, signature); #else diff --git a/src/JsonWebToken/Cryptography/EcdsaSigner.cs b/src/JsonWebToken/Cryptography/EcdsaSigner.cs index 68d7581f..0a51dfd2 100644 --- a/src/JsonWebToken/Cryptography/EcdsaSigner.cs +++ b/src/JsonWebToken/Cryptography/EcdsaSigner.cs @@ -3,6 +3,7 @@ #if SUPPORT_ELLIPTIC_CURVE_SIGNATURE using System; +using System.Buffers; using System.Diagnostics; using System.Security.Cryptography; @@ -45,9 +46,10 @@ public override bool TrySign(ReadOnlySpan data, Span destination, ou var ecdsa = _ecdsaPool.Get(); #if SUPPORT_SPAN_CRYPTO - Span hash = stackalloc byte[_sha.HashSize]; - _sha.ComputeHash(data, hash); - return ecdsa.TrySignHash(hash, destination, out bytesWritten); + Span hash = stackalloc byte[Sha2.HashSizeStackallocThreshold]; + hash = hash.Slice(0, _sha.HashSize); + _sha.ComputeHash(data, hash); + return ecdsa.TrySignHash(hash, destination, out bytesWritten); #else byte[] hash = new byte[_sha.HashSize]; _sha.ComputeHash(data, hash); @@ -72,6 +74,6 @@ protected override void Dispose(bool disposing) } } - } + } } #endif \ No newline at end of file diff --git a/src/JsonWebToken/Cryptography/EllipticalCurves.cs b/src/JsonWebToken/Cryptography/EllipticalCurves.cs index 44c920c0..90c396fa 100644 --- a/src/JsonWebToken/Cryptography/EllipticalCurves.cs +++ b/src/JsonWebToken/Cryptography/EllipticalCurves.cs @@ -4,6 +4,7 @@ #if SUPPORT_ELLIPTIC_CURVE using System; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Text.Json; @@ -11,7 +12,7 @@ namespace JsonWebToken { /// Elliptical Curve Types. - public readonly struct EllipticalCurve + public sealed class EllipticalCurve { [MagicNumber("-256")] private const uint _256 = 909455917u; @@ -25,46 +26,58 @@ public readonly struct EllipticalCurve [MagicNumber("ecp256k1")] private const ulong ecp256k1 = 3560999532473901925ul; + /// No curve defined. + internal static readonly EllipticalCurve Empty = new EllipticalCurve(id: 0, namedCurve: default, name: default, + supportedSignatureAlgorithm: SignatureAlgorithm.None, keySizeInBits: 0, hashSize: 0, canonicalizeSize: 35); + /// 'P-256'. - public static readonly EllipticalCurve P256 = new EllipticalCurve(1, ECCurve.NamedCurves.nistP256, EllipticalCurveNames.P256, 256, 64, SignatureAlgorithm.ES256); + public static readonly EllipticalCurve P256 = new EllipticalCurve(id: 1, namedCurve: ECCurve.NamedCurves.nistP256, name: EllipticalCurveNames.P256, + supportedSignatureAlgorithm: SignatureAlgorithm.ES256, keySizeInBits: 256, hashSize: 64, canonicalizeSize: 126); /// 'P-384'. - public static readonly EllipticalCurve P384 = new EllipticalCurve(2, ECCurve.NamedCurves.nistP384, EllipticalCurveNames.P384, 384, 96, SignatureAlgorithm.ES384); + public static readonly EllipticalCurve P384 = new EllipticalCurve(id: 2, namedCurve: ECCurve.NamedCurves.nistP384, name: EllipticalCurveNames.P384, + supportedSignatureAlgorithm: SignatureAlgorithm.ES384, keySizeInBits: 384, hashSize: 96, canonicalizeSize: 168); /// 'P-521'. - public static readonly EllipticalCurve P521 = new EllipticalCurve(3, ECCurve.NamedCurves.nistP521, EllipticalCurveNames.P521, 521, 132, SignatureAlgorithm.ES512); + public static readonly EllipticalCurve P521 = new EllipticalCurve(id: 3, namedCurve: ECCurve.NamedCurves.nistP521, name: EllipticalCurveNames.P521, + supportedSignatureAlgorithm: SignatureAlgorithm.ES512, keySizeInBits: 521, hashSize: 132, canonicalizeSize: 216); /// 'secp256k1'. - public static readonly EllipticalCurve Secp256k1 = new EllipticalCurve(8, ECCurve.CreateFromValue("1.3.132.0.10"), EllipticalCurveNames.Secp256k1, 256, 64, SignatureAlgorithm.ES256K); + public static readonly EllipticalCurve Secp256k1 = new EllipticalCurve(id: 8, namedCurve: ECCurve.CreateFromValue("1.3.132.0.10"), name: EllipticalCurveNames.Secp256k1, + supportedSignatureAlgorithm: SignatureAlgorithm.ES256K, keySizeInBits: 256, hashSize: 64, canonicalizeSize: 130); /// Initializes a new instance of the struct. - public EllipticalCurve(byte id, ECCurve namedCurve, JsonEncodedText name, int keySizeInBits, int hashSize, SignatureAlgorithm supportedSignatureAlgorithm) + private EllipticalCurve(byte id, ECCurve namedCurve, JsonEncodedText name, SignatureAlgorithm supportedSignatureAlgorithm, int keySizeInBits, int hashSize, int canonicalizeSize) { Id = id; KeySizeInBits = keySizeInBits; Name = name; CurveParameters = namedCurve; HashSize = hashSize; + CanonicalizeSize = canonicalizeSize; SupportedSignatureAlgorithm = supportedSignatureAlgorithm; } /// The name of the curve. - public readonly JsonEncodedText Name; + public JsonEncodedText Name { get; } /// The internal id of the curve. - public readonly byte Id; + public byte Id { get; } /// The size of the key, in bits. - public readonly int KeySizeInBits; + public int KeySizeInBits { get; } /// The parameters curve. - public readonly ECCurve CurveParameters; + public ECCurve CurveParameters { get; } /// The size of the resulting hash. - public readonly int HashSize; + public int HashSize { get; } + + /// The size of the canonicalized form. + public int CanonicalizeSize { get; } /// The supported for this curve. - public readonly SignatureAlgorithm SupportedSignatureAlgorithm; + public SignatureAlgorithm SupportedSignatureAlgorithm { get; } /// The supported s. public static ReadOnlyCollection SupportedCurves => Array.AsReadOnly(_supportedCurves); @@ -88,7 +101,6 @@ public EllipticalCurve(byte id, ECCurve namedCurve, JsonEncodedText name, int ke /// Returns the corresponding to the . public static EllipticalCurve FromString(string crv) { - if (!TryParse(crv, out var curve)) { ThrowHelper.ThrowNotSupportedException_Curve(crv); @@ -98,7 +110,7 @@ public static EllipticalCurve FromString(string crv) } /// Tries to parse a into a . - public static bool TryParse(string crv, out EllipticalCurve curve) + public static bool TryParse(string crv, [NotNullWhen(true)] out EllipticalCurve? curve) { switch (crv) { @@ -115,7 +127,7 @@ public static bool TryParse(string crv, out EllipticalCurve curve) curve = Secp256k1; goto Parsed; default: - curve = default; + curve = null; return false; } @@ -149,11 +161,11 @@ internal static EllipticalCurve FromSpan(ReadOnlySpan crv) } ThrowHelper.ThrowNotSupportedException_Curve(Utf8.GetString(crv)); - return default; + return null; } /// Gets the supported for the provided . - public static bool TryGetSupportedCurve(SignatureAlgorithm algorithm, out EllipticalCurve curve) + public static bool TryGetSupportedCurve(SignatureAlgorithm algorithm, [NotNullWhen(true)] out EllipticalCurve? curve) { for (int i = 0; i < _supportedCurves.Length; i++) { @@ -165,7 +177,7 @@ public static bool TryGetSupportedCurve(SignatureAlgorithm algorithm, out Ellipt } } - curve = default; + curve = null; return false; } diff --git a/src/JsonWebToken/Cryptography/Hmac.cs b/src/JsonWebToken/Cryptography/Hmac.cs index 314b4ff1..253d6ebf 100644 --- a/src/JsonWebToken/Cryptography/Hmac.cs +++ b/src/JsonWebToken/Cryptography/Hmac.cs @@ -25,14 +25,14 @@ internal readonly ref struct Hmac public Hmac(Sha2 sha2, ReadOnlySpan key, Span hmacKey) { Debug.Assert(sha2 != null); - Debug.Assert(hmacKey.Length == sha2!.BlockSize * 2); + Debug.Assert(hmacKey.Length >= sha2.BlockSize * 2); Sha2 = sha2; _keys = hmacKey; int blockSize = sha2.BlockSize; if (key.Length > blockSize) { - Span keyPrime = stackalloc byte[blockSize]; + Span keyPrime = stackalloc byte[Sha2.BlockSizeStackallocThreshold * 2].Slice(0, blockSize); HmacHelper.InitializeIOKeys(keyPrime, _keys, blockSize); keyPrime.Clear(); } @@ -53,7 +53,7 @@ public void ComputeHash(ReadOnlySpan source, Span destination) { Span W = size > Constants.MaxStackallocBytes ? (arrayToReturn = ArrayPool.Shared.Rent(size)) - : stackalloc byte[size]; + : stackalloc byte[Constants.MaxStackallocBytes]; Sha2.ComputeHash(source, _keys.Slice(0, blockSize), destination, W); Sha2.ComputeHash(destination, _keys.Slice(blockSize, blockSize), destination, W); } diff --git a/src/JsonWebToken/Cryptography/HmacSha2.cs b/src/JsonWebToken/Cryptography/HmacSha2.cs index 36c7b932..45712733 100644 --- a/src/JsonWebToken/Cryptography/HmacSha2.cs +++ b/src/JsonWebToken/Cryptography/HmacSha2.cs @@ -40,7 +40,7 @@ public HmacSha2(Sha2 sha2, ReadOnlySpan key) _outerPadKey = new ReadOnlyMemory(_keys, blockSize, blockSize); if (key.Length > blockSize) { - Span keyPrime = stackalloc byte[sha2.HashSize]; + Span keyPrime = stackalloc byte[Sha2.HashSizeStackallocThreshold].Slice(0, sha2.HashSize); Sha2.ComputeHash(key, default, keyPrime, default); HmacHelper.InitializeIOKeys(keyPrime, _keys, blockSize); keyPrime.Clear(); @@ -61,7 +61,7 @@ public void ComputeHash(ReadOnlySpan source, Span destination) { Span W = size > Constants.MaxStackallocBytes ? (arrayToReturn = ArrayPool.Shared.Rent(size)) - : stackalloc byte[size]; + : stackalloc byte[Constants.MaxStackallocBytes]; Sha2.ComputeHash(source, _innerPadKey.Span, destination, W); Sha2.ComputeHash(destination, _outerPadKey.Span, destination, W); } diff --git a/src/JsonWebToken/Cryptography/KeyWrapper.cs b/src/JsonWebToken/Cryptography/KeyWrapper.cs index 2bbcc7b5..f128d2d5 100644 --- a/src/JsonWebToken/Cryptography/KeyWrapper.cs +++ b/src/JsonWebToken/Cryptography/KeyWrapper.cs @@ -53,5 +53,7 @@ public void Dispose() [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static SymmetricJwk CreateSymmetricKey(EncryptionAlgorithm encryptionAlgorithm, SymmetricJwk? staticKey) => staticKey ?? SymmetricJwk.GenerateKey(encryptionAlgorithm.RequiredKeySizeInBits, computeThumbprint: false); + + internal const int WrappedKeySizeStackallocThreshold = 72; } } diff --git a/src/JsonWebToken/Cryptography/Pbes2KeyUnwrapper.cs b/src/JsonWebToken/Cryptography/Pbes2KeyUnwrapper.cs index afdeec01..e1f3d3e9 100644 --- a/src/JsonWebToken/Cryptography/Pbes2KeyUnwrapper.cs +++ b/src/JsonWebToken/Cryptography/Pbes2KeyUnwrapper.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE in the project root for license information. using System; +using System.Buffers; using System.Diagnostics; using System.Security.Cryptography; using System.Text.Json; @@ -10,6 +11,8 @@ namespace JsonWebToken.Cryptography { internal sealed class Pbes2KeyUnwrapper : KeyUnwrapper { + private const int KeySizeThreshold = 32; + private readonly JsonEncodedText _algorithm; private readonly int _algorithmNameLength; private readonly int _keySizeInBytes; @@ -53,18 +56,32 @@ public override bool TryUnwrapKey(ReadOnlySpan keyBytes, Span destin int iterationCount = (int)p2c.GetInt64(); var b64p2s = p2s.GetRawValue(); int p2sLength = Base64Url.GetArraySizeRequiredToDecode(b64p2s.Length); + + int saltLength = p2sLength + 1 + _algorithmNameLength; + byte[]? saltArray = null; + Span salt = saltLength > Pbkdf2.SaltSizeThreshold + 18 + 1 + ? (saltArray = ArrayPool.Shared.Rent(saltLength)) + : stackalloc byte[Pbkdf2.SaltSizeThreshold + 18 + 1]; + try + { + salt = salt.Slice(0, saltLength); + Base64Url.Decode(b64p2s.Span, salt.Slice(_algorithmNameLength + 1)); + salt[_algorithmNameLength] = 0x00; + _algorithm.EncodedUtf8Bytes.CopyTo(salt); + Span derivedKey = stackalloc byte[KeySizeThreshold].Slice(0, _keySizeInBytes); + Pbkdf2.DeriveKey(_password, salt, _hashAlgorithm, (uint)iterationCount, derivedKey); - Span salt = stackalloc byte[p2sLength + 1 + _algorithmNameLength]; - Base64Url.Decode(b64p2s.Span, salt.Slice(_algorithmNameLength + 1)); - salt[_algorithmNameLength] = 0x00; - _algorithm.EncodedUtf8Bytes.CopyTo(salt); - - Span derivedKey = stackalloc byte[_keySizeInBytes]; - Pbkdf2.DeriveKey(_password, salt, _hashAlgorithm, (uint)iterationCount, derivedKey); - - using var keyUnwrapper = new AesKeyUnwrapper(derivedKey, EncryptionAlgorithm, _keyManagementAlgorithm); - return keyUnwrapper.TryUnwrapKey(keyBytes, destination, header, out bytesWritten); + using var keyUnwrapper = new AesKeyUnwrapper(derivedKey, EncryptionAlgorithm, _keyManagementAlgorithm); + return keyUnwrapper.TryUnwrapKey(keyBytes, destination, header, out bytesWritten); + } + finally + { + if (saltArray != null) + { + ArrayPool.Shared.Return(saltArray); + } + } } protected override void Dispose(bool disposing) diff --git a/src/JsonWebToken/Cryptography/Pbes2KeyWrapper.cs b/src/JsonWebToken/Cryptography/Pbes2KeyWrapper.cs index 1939d83e..1157f0a9 100644 --- a/src/JsonWebToken/Cryptography/Pbes2KeyWrapper.cs +++ b/src/JsonWebToken/Cryptography/Pbes2KeyWrapper.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE in the project root for license information. using System; +using System.Buffers; using System.Diagnostics; using System.Security.Cryptography; using System.Text.Json; @@ -10,6 +11,8 @@ namespace JsonWebToken.Cryptography { internal sealed class Pbes2KeyWrapper : KeyWrapper { + private const int KeySizeThreshold = 32; + private readonly JsonEncodedText _algorithm; private readonly int _algorithmNameLength; private readonly int _keySizeInBytes; @@ -57,22 +60,46 @@ public override SymmetricJwk WrapKey(Jwk? staticKey, JwtHeader header, Span buffer = stackalloc byte[_saltSizeInBytes + 1 + _algorithmNameLength]; + int bufferSize = _saltSizeInBytes + _algorithmNameLength + 1; + byte[]? bufferToReturn = null; + Span buffer = bufferSize > Pbkdf2.SaltSizeThreshold + 18 + 1 // 18 = max alg name length + ? (bufferToReturn = ArrayPool.Shared.Rent(bufferSize)) + : stackalloc byte[Pbkdf2.SaltSizeThreshold + 18 + 1]; + buffer = buffer.Slice(0, bufferSize); _saltGenerator.Generate(buffer.Slice(_algorithmNameLength + 1)); buffer[_algorithmNameLength] = 0x00; _algorithm.EncodedUtf8Bytes.CopyTo(buffer); - Span derivedKey = stackalloc byte[_keySizeInBytes]; + Span derivedKey = stackalloc byte[KeySizeThreshold].Slice(0, _keySizeInBytes); Pbkdf2.DeriveKey(_password, buffer, _hashAlgorithm, _iterationCount, derivedKey); Span salt = buffer.Slice(_algorithmNameLength + 1, _saltSizeInBytes); - Span b64Salt = stackalloc byte[Base64Url.GetArraySizeRequiredToEncode(salt.Length)]; - Base64Url.Encode(salt, b64Salt); - header.Add(JwtHeaderParameterNames.P2s, Utf8.GetString(b64Salt)); - header.Add(JwtHeaderParameterNames.P2c, _iterationCount); + int saltLengthB64 = Base64Url.GetArraySizeRequiredToEncode(salt.Length); + byte[]? arrayToReturn = null; + Span b64Salt = saltLengthB64 > Pbkdf2.SaltSizeThreshold * 4 / 3 + ? (arrayToReturn = ArrayPool.Shared.Rent(saltLengthB64)) + : stackalloc byte[Pbkdf2.SaltSizeThreshold * 4 / 3]; + try + { + int length = Base64Url.Encode(salt, b64Salt); + header.Add(JwtHeaderParameterNames.P2s, Utf8.GetString(b64Salt.Slice(0, saltLengthB64))); + header.Add(JwtHeaderParameterNames.P2c, _iterationCount); + + using var keyWrapper = new AesKeyWrapper(derivedKey, EncryptionAlgorithm, _keyManagementAlgorithm); + return keyWrapper.WrapKey(contentEncryptionKey, header, destination); + } + finally + { + if (bufferToReturn != null) + { + ArrayPool.Shared.Return(bufferToReturn); + } - using var keyWrapper = new AesKeyWrapper(derivedKey, EncryptionAlgorithm, _keyManagementAlgorithm); - return keyWrapper.WrapKey(contentEncryptionKey, header, destination); + if (arrayToReturn != null) + { + ArrayPool.Shared.Return(arrayToReturn); + } + } } private static HashAlgorithmName GetHashAlgorithm(EncryptionAlgorithm encryptionAlgorithm) diff --git a/src/JsonWebToken/Cryptography/Pbkdf2.cs b/src/JsonWebToken/Cryptography/Pbkdf2.cs index 49bbe712..07bfa9b0 100644 --- a/src/JsonWebToken/Cryptography/Pbkdf2.cs +++ b/src/JsonWebToken/Cryptography/Pbkdf2.cs @@ -14,6 +14,8 @@ namespace JsonWebToken.Cryptography // http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf internal static class Pbkdf2 { + internal const int SaltSizeThreshold = 32; + public static void DeriveKey(byte[] password, ReadOnlySpan salt, Sha2 prf, uint iterationCount, Span destination) { Debug.Assert(password != null); @@ -23,22 +25,29 @@ public static void DeriveKey(byte[] password, ReadOnlySpan salt, Sha2 prf, int numBytesWritten = 0; int numBytesRemaining = destination.Length; - Span saltWithBlockIndex = stackalloc byte[checked(salt.Length + sizeof(uint))]; - salt.CopyTo(saltWithBlockIndex); - - Span hmacKey = stackalloc byte[prf.BlockSize * 2]; - var hashAlgorithm = new Hmac(prf, password, hmacKey); - - int wSize = prf.GetWorkingSetSize(int.MaxValue); - int blockSize = hashAlgorithm.BlockSize; + int saltWithBlockLength = salt.Length + sizeof(uint); + byte[]? saltArray = null; byte[]? arrayToReturn = null; try { + Span saltWithBlockIndex = saltWithBlockLength > SaltSizeThreshold + ? (saltArray = ArrayPool.Shared.Rent(saltWithBlockLength)) + : stackalloc byte[SaltSizeThreshold]; + saltWithBlockIndex = saltWithBlockIndex.Slice(0, saltWithBlockLength); + salt.CopyTo(saltWithBlockIndex); + + Span hmacKey = stackalloc byte[Sha2.BlockSizeStackallocThreshold * 2]; + var hashAlgorithm = new Hmac(prf, password, hmacKey.Slice(0, prf.BlockSize * 2)); + + int wSize = prf.GetWorkingSetSize(int.MaxValue); + int blockSize = hashAlgorithm.BlockSize; + Span W = wSize > Constants.MaxStackallocBytes ? (arrayToReturn = ArrayPool.Shared.Rent(wSize)) - : stackalloc byte[wSize]; - Span currentBlock = stackalloc byte[hashAlgorithm.HashSize]; - Span iterationBlock = stackalloc byte[hashAlgorithm.HashSize]; + : stackalloc byte[Constants.MaxStackallocBytes]; + W = W.Slice(0, wSize); + Span currentBlock = stackalloc byte[Sha2.HashSizeStackallocThreshold].Slice(0, hashAlgorithm.HashSize); + Span iterationBlock = stackalloc byte[Sha2.HashSizeStackallocThreshold].Slice(0, hashAlgorithm.HashSize); Span blockIndexDestination = saltWithBlockIndex.Slice(saltWithBlockIndex.Length - sizeof(uint)); for (uint blockIndex = 1; numBytesRemaining > 0; blockIndex++) { @@ -59,6 +68,11 @@ public static void DeriveKey(byte[] password, ReadOnlySpan salt, Sha2 prf, } finally { + if (saltArray != null) + { + ArrayPool.Shared.Return(saltArray); + } + if (arrayToReturn != null) { ArrayPool.Shared.Return(arrayToReturn); diff --git a/src/JsonWebToken/Cryptography/RsaKeyUnwrapper.cs b/src/JsonWebToken/Cryptography/RsaKeyUnwrapper.cs index 4a936547..691b676c 100644 --- a/src/JsonWebToken/Cryptography/RsaKeyUnwrapper.cs +++ b/src/JsonWebToken/Cryptography/RsaKeyUnwrapper.cs @@ -71,9 +71,9 @@ public override bool TryUnwrapKey(ReadOnlySpan key, Span destination try { // RSA up through 4096 stackalloc - if (_rsa.KeySize <= 4096) + if (_rsa.KeySize <= Constants.MaxStackallocBytes * 8 * 2) { - tmp = stackalloc byte[keySizeBytes]; + tmp = stackalloc byte[Constants.MaxStackallocBytes * 2]; } else { diff --git a/src/JsonWebToken/Cryptography/RsaSignatureVerifier.cs b/src/JsonWebToken/Cryptography/RsaSignatureVerifier.cs index 7aa01dbf..54051e3b 100644 --- a/src/JsonWebToken/Cryptography/RsaSignatureVerifier.cs +++ b/src/JsonWebToken/Cryptography/RsaSignatureVerifier.cs @@ -58,7 +58,7 @@ public override bool Verify(ReadOnlySpan data, ReadOnlySpan signatur try { #if SUPPORT_SPAN_CRYPTO - Span hash = stackalloc byte[_sha.HashSize]; + Span hash = stackalloc byte[Sha2.HashSizeStackallocThreshold].Slice(0, _sha.HashSize); _sha.ComputeHash(data, hash); return rsa.VerifyHash(hash, signature, _hashAlgorithm, _signaturePadding); #else diff --git a/src/JsonWebToken/Cryptography/RsaSigner.cs b/src/JsonWebToken/Cryptography/RsaSigner.cs index 661647ed..0dc44f4a 100644 --- a/src/JsonWebToken/Cryptography/RsaSigner.cs +++ b/src/JsonWebToken/Cryptography/RsaSigner.cs @@ -56,7 +56,7 @@ public override bool TrySign(ReadOnlySpan data, Span destination, ou try { #if SUPPORT_SPAN_CRYPTO - Span hash = stackalloc byte[_sha.HashSize]; + Span hash = stackalloc byte[Sha2.HashSizeStackallocThreshold].Slice(0, _sha.HashSize); _sha.ComputeHash(data, hash); return rsa.TrySignHash(hash, destination, _hashAlgorithm, _signaturePadding, out bytesWritten); #else diff --git a/src/JsonWebToken/Cryptography/Sha2.cs b/src/JsonWebToken/Cryptography/Sha2.cs index 5337185d..c3ab0c40 100644 --- a/src/JsonWebToken/Cryptography/Sha2.cs +++ b/src/JsonWebToken/Cryptography/Sha2.cs @@ -29,6 +29,9 @@ public abstract class Sha2 /// The size of the resulting hash. public abstract int BlockSize { get; } + internal const int HashSizeStackallocThreshold = Sha512.Sha512HashSize; + internal const int BlockSizeStackallocThreshold = Sha512.Sha512BlockSize; + #if SUPPORT_SIMD private static ReadOnlySpan LittleEndianUInt64 => new byte[32] { diff --git a/src/JsonWebToken/Cryptography/Sha384.cs b/src/JsonWebToken/Cryptography/Sha384.cs index 54131d9b..efca1d96 100644 --- a/src/JsonWebToken/Cryptography/Sha384.cs +++ b/src/JsonWebToken/Cryptography/Sha384.cs @@ -95,7 +95,9 @@ public override void ComputeHash(ReadOnlySpan source, ReadOnlySpan p ref byte lastBlockRef = ref MemoryMarshal.GetReference(lastBlock); // update - Span wTemp = workingSet.Length < IterationCount * sizeof(ulong) ? stackalloc byte[IterationCount * sizeof(ulong)] : workingSet; + Span wTemp = workingSet.Length < IterationCount * sizeof(ulong) + ? stackalloc byte[IterationCount * sizeof(ulong)] + : workingSet; ref ulong wRef = ref Unsafe.As(ref MemoryMarshal.GetReference(wTemp)); ref ulong stateRef = ref MemoryMarshal.GetReference(state); ref byte srcStartRef = ref MemoryMarshal.GetReference(source); diff --git a/src/JsonWebToken/Cryptography/Sha512.cs b/src/JsonWebToken/Cryptography/Sha512.cs index 77bc6db0..8072cd88 100644 --- a/src/JsonWebToken/Cryptography/Sha512.cs +++ b/src/JsonWebToken/Cryptography/Sha512.cs @@ -97,7 +97,9 @@ public override void ComputeHash(ReadOnlySpan source, ReadOnlySpan p ref byte lastBlockRef = ref MemoryMarshal.GetReference(lastBlock); // update - Span wTemp = workingSet.Length < IterationCount * sizeof(ulong) ? stackalloc byte[IterationCount * sizeof(ulong)] : workingSet; + Span wTemp = workingSet.Length < IterationCount * sizeof(ulong) + ? stackalloc byte[IterationCount * sizeof(ulong)] + : workingSet; ref ulong wRef = ref Unsafe.As(ref MemoryMarshal.GetReference(wTemp)); ref ulong stateRef = ref MemoryMarshal.GetReference(state); ref byte srcStartRef = ref MemoryMarshal.GetReference(source); diff --git a/src/JsonWebToken/Cryptography/Signer.cs b/src/JsonWebToken/Cryptography/Signer.cs index bcb34d3d..36424f52 100644 --- a/src/JsonWebToken/Cryptography/Signer.cs +++ b/src/JsonWebToken/Cryptography/Signer.cs @@ -71,5 +71,7 @@ protected override void Dispose(bool disposing) { } } + + internal const int SignatureStackallocThreshold = 256; } } diff --git a/src/JsonWebToken/Cryptography/SymmetricSignatureVerifier.cs b/src/JsonWebToken/Cryptography/SymmetricSignatureVerifier.cs index 6405df95..607fddfa 100644 --- a/src/JsonWebToken/Cryptography/SymmetricSignatureVerifier.cs +++ b/src/JsonWebToken/Cryptography/SymmetricSignatureVerifier.cs @@ -63,7 +63,7 @@ public override bool Verify(ReadOnlySpan input, ReadOnlySpan signatu { Debug.Assert(!_disposed); - Span hash = stackalloc byte[_hashSizeInBytes]; + Span hash = stackalloc byte[Sha2.HashSizeStackallocThreshold].Slice(0, _hashSizeInBytes); _hashAlgorithm.ComputeHash(input, hash); return CryptographicOperations.FixedTimeEquals(signature, hash); } diff --git a/src/JsonWebToken/ECJwk.cs b/src/JsonWebToken/ECJwk.cs index bfcd0918..bf17816f 100644 --- a/src/JsonWebToken/ECJwk.cs +++ b/src/JsonWebToken/ECJwk.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -14,7 +15,6 @@ namespace JsonWebToken { - //#nullable disable /// Represents an Elliptic Curve JSON Web Key as defined in https://tools.ietf.org/html/rfc7518#section-6. public sealed class ECJwk : AsymmetricJwk, IJwtSerializable { @@ -27,37 +27,37 @@ public sealed class ECJwk : AsymmetricJwk, IJwtSerializable #endif /// Initializes a new instance of . - private ECJwk(ECParameters parameters) + private ECJwk(in ECParameters parameters) { Initialize(parameters); } /// Initializes a new instance of . - private ECJwk(in EllipticalCurve crv, byte[] d, byte[] x, byte[] y) + private ECJwk(EllipticalCurve crv, byte[] d, byte[] x, byte[] y) { Initialize(crv, d, x, y); } /// Initializes a new instance of . - private ECJwk(in EllipticalCurve crv, string d, string x, string y) + private ECJwk(EllipticalCurve crv, string d, string x, string y) { Initialize(crv, d, x, y); } /// Initializes a new instance of . No private key is provided. - private ECJwk(in EllipticalCurve crv, byte[] x, byte[] y) + private ECJwk(EllipticalCurve crv, byte[] x, byte[] y) { Initialize(crv, x, y); } /// Initializes a new instance of . No private key is provided. - private ECJwk(in EllipticalCurve crv, string x, string y) + private ECJwk(EllipticalCurve crv, string x, string y) { Initialize(crv, x, y); } /// Initializes a new instance of . - private ECJwk(ECParameters parameters, SignatureAlgorithm alg) + private ECJwk(in ECParameters parameters, SignatureAlgorithm alg) : base(alg) { Initialize(parameters); @@ -68,7 +68,7 @@ private ECJwk(ECParameters parameters, SignatureAlgorithm alg) } /// Initializes a new instance of . - private ECJwk(in EllipticalCurve crv, byte[] d, byte[] x, byte[] y, SignatureAlgorithm alg) + private ECJwk(EllipticalCurve crv, byte[] d, byte[] x, byte[] y, SignatureAlgorithm alg) : base(alg) { Initialize(crv, d, x, y, alg); @@ -79,7 +79,7 @@ private ECJwk(in EllipticalCurve crv, byte[] d, byte[] x, byte[] y, SignatureAlg } /// Initializes a new instance of . - private ECJwk(in EllipticalCurve crv, string d, string x, string y, SignatureAlgorithm alg) + private ECJwk(EllipticalCurve crv, string d, string x, string y, SignatureAlgorithm alg) : base(alg) { Initialize(crv, d, x, y, alg); @@ -90,7 +90,7 @@ private ECJwk(in EllipticalCurve crv, string d, string x, string y, SignatureAlg } /// Initializes a new instance of . No private key is provided. - private ECJwk(in EllipticalCurve crv, byte[] x, byte[] y, SignatureAlgorithm alg) + private ECJwk(EllipticalCurve crv, byte[] x, byte[] y, SignatureAlgorithm alg) : base(alg) { Initialize(crv, x, y, alg); @@ -101,7 +101,7 @@ private ECJwk(in EllipticalCurve crv, byte[] x, byte[] y, SignatureAlgorithm alg } /// Initializes a new instance of . No private key is provided. - private ECJwk(in EllipticalCurve crv, string x, string y, SignatureAlgorithm alg) + private ECJwk(EllipticalCurve crv, string x, string y, SignatureAlgorithm alg) : base(alg) { Initialize(crv, x, y, alg); @@ -112,7 +112,7 @@ private ECJwk(in EllipticalCurve crv, string x, string y, SignatureAlgorithm alg } /// Initializes a new instance of . - private ECJwk(ECParameters parameters, KeyManagementAlgorithm alg) + private ECJwk(in ECParameters parameters, KeyManagementAlgorithm alg) : base(alg) { Initialize(parameters); @@ -123,7 +123,7 @@ private ECJwk(ECParameters parameters, KeyManagementAlgorithm alg) } /// Initializes a new instance of . - private ECJwk(in EllipticalCurve crv, byte[] d, byte[] x, byte[] y, KeyManagementAlgorithm alg) + private ECJwk(EllipticalCurve crv, byte[] d, byte[] x, byte[] y, KeyManagementAlgorithm alg) : base(alg) { Initialize(crv, d, x, y); @@ -134,7 +134,7 @@ private ECJwk(in EllipticalCurve crv, byte[] d, byte[] x, byte[] y, KeyManagemen } /// Initializes a new instance of . - private ECJwk(in EllipticalCurve crv, string d, string x, string y, KeyManagementAlgorithm alg) + private ECJwk(EllipticalCurve crv, string d, string x, string y, KeyManagementAlgorithm alg) : base(alg) { Initialize(crv, d, x, y); @@ -145,7 +145,7 @@ private ECJwk(in EllipticalCurve crv, string d, string x, string y, KeyManagemen } /// Initializes a new instance of . No private key is provided. - private ECJwk(in EllipticalCurve crv, byte[] x, byte[] y, KeyManagementAlgorithm alg) + private ECJwk(EllipticalCurve crv, byte[] x, byte[] y, KeyManagementAlgorithm alg) : base(alg) { Initialize(crv, x, y); @@ -156,7 +156,7 @@ private ECJwk(in EllipticalCurve crv, byte[] x, byte[] y, KeyManagementAlgorithm } /// Initializes a new instance of . No private key is provided. - private ECJwk(in EllipticalCurve crv, string x, string y, KeyManagementAlgorithm alg) + private ECJwk(EllipticalCurve crv, string x, string y, KeyManagementAlgorithm alg) : base(alg) { Initialize(crv, x, y); @@ -169,10 +169,11 @@ private ECJwk(in EllipticalCurve crv, string x, string y, KeyManagementAlgorithm /// Initializes a new instance of . private ECJwk() { + Crv = EllipticalCurve.Empty; } -#nullable enable - private void Initialize(ECParameters parameters) + [MemberNotNull(nameof(Crv))] + private void Initialize(in ECParameters parameters) { parameters.Validate(); @@ -190,8 +191,14 @@ private void Initialize(ECParameters parameters) }; } - private void Initialize(in EllipticalCurve crv, string x, string y, SignatureAlgorithm alg) + [MemberNotNull(nameof(Crv))] + private void Initialize(EllipticalCurve crv, string x, string y, SignatureAlgorithm alg) { + if (crv is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.crv); + } + if (crv.SupportedSignatureAlgorithm != alg) { ThrowHelper.ThrowNotSupportedException_SignatureAlgorithm(alg, crv); @@ -199,7 +206,9 @@ private void Initialize(in EllipticalCurve crv, string x, string y, SignatureAlg Initialize(crv, x, y); } - private void Initialize(in EllipticalCurve crv, string d, string x, string y, SignatureAlgorithm alg) + + [MemberNotNull(nameof(Crv))] + private void Initialize(EllipticalCurve crv, string d, string x, string y, SignatureAlgorithm alg) { if (d is null) { @@ -210,8 +219,14 @@ private void Initialize(in EllipticalCurve crv, string d, string x, string y, Si Initialize(crv, x, y, alg); } - private void Initialize(in EllipticalCurve crv, string x, string y) + [MemberNotNull(nameof(Crv))] + private void Initialize(EllipticalCurve crv, string x, string y) { + if (crv is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.crv); + } + if (x is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.x); @@ -228,7 +243,8 @@ private void Initialize(in EllipticalCurve crv, string x, string y) _parameters.Q.Y = Base64Url.Decode(y); } - private void Initialize(in EllipticalCurve crv, string d, string x, string y) + [MemberNotNull(nameof(Crv))] + private void Initialize(EllipticalCurve crv, string d, string x, string y) { if (d is null) { @@ -239,8 +255,14 @@ private void Initialize(in EllipticalCurve crv, string d, string x, string y) Initialize(crv, x, y); } - private void Initialize(in EllipticalCurve crv, byte[] x, byte[] y, SignatureAlgorithm alg) + [MemberNotNull(nameof(Crv))] + private void Initialize(EllipticalCurve crv, byte[] x, byte[] y, SignatureAlgorithm alg) { + if (crv is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.crv); + } + if (crv.SupportedSignatureAlgorithm != alg) { ThrowHelper.ThrowNotSupportedException_SignatureAlgorithm(alg, crv); @@ -249,15 +271,22 @@ private void Initialize(in EllipticalCurve crv, byte[] x, byte[] y, SignatureAlg Initialize(crv, x, y); } - private void Initialize(in EllipticalCurve crv, byte[] d, byte[] x, byte[] y, SignatureAlgorithm alg) + [MemberNotNull(nameof(Crv))] + private void Initialize(EllipticalCurve crv, byte[] d, byte[] x, byte[] y, SignatureAlgorithm alg) { _parameters.D = d; Initialize(crv, x, y, alg); } - private void Initialize(in EllipticalCurve crv, byte[] x, byte[] y) + [MemberNotNull(nameof(Crv))] + private void Initialize(EllipticalCurve crv, byte[] x, byte[] y) { - if (x is null) + if (crv is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.crv); + } + + if (x is null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.x); } @@ -271,9 +300,11 @@ private void Initialize(in EllipticalCurve crv, byte[] x, byte[] y) _parameters.Curve = crv.CurveParameters; _parameters.Q.X = x; _parameters.Q.Y = y; + _parameters.Validate(); } - private void Initialize(in EllipticalCurve crv, byte[] d, byte[] x, byte[] y) + [MemberNotNull(nameof(Crv))] + private void Initialize(EllipticalCurve crv, byte[] d, byte[] x, byte[] y) { if (d is null) { @@ -294,6 +325,7 @@ private void Initialize(in EllipticalCurve crv, byte[] d, byte[] x, byte[] y) public ReadOnlySpan D => _parameters.D; /// Gets or sets the 'crv' (Curve). + [MemberNotNull] public EllipticalCurve Crv { get; private set; } /// Gets or sets the 'x' (X Coordinate). @@ -328,7 +360,6 @@ public override bool SupportSignature(SignatureAlgorithm algorithm) => false; #endif - /// public override bool SupportKeyManagement(KeyManagementAlgorithm algorithm) #if SUPPORT_ELLIPTIC_CURVE_KEYWRAPPING @@ -337,7 +368,6 @@ public override bool SupportKeyManagement(KeyManagementAlgorithm algorithm) => false; #endif - /// public override bool SupportEncryption(EncryptionAlgorithm algorithm) => false; @@ -377,14 +407,11 @@ protected override KeyUnwrapper CreateKeyUnwrapper(EncryptionAlgorithm encryptio /// Exports the key parameters. public ECParameters ExportParameters(bool includePrivateParameters = false) { - var parameters = new ECParameters - { - Q = _parameters.Q, - Curve = _parameters.Curve - }; - if (includePrivateParameters) + var parameters = _parameters; + + if (!includePrivateParameters) { - parameters.D = _parameters.D; + parameters.D = null; } return parameters; @@ -399,11 +426,11 @@ public static ECJwk GeneratePrivateKey(SignatureAlgorithm algorithm, bool comput => GenerateKey(algorithm, withPrivateKey: true, computeThumbprint: computeThumbprint); /// Generates a private . - public static ECJwk GeneratePrivateKey(in EllipticalCurve curve, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) + public static ECJwk GeneratePrivateKey(EllipticalCurve curve, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) => GenerateKey(curve, algorithm, withPrivateKey: true, computeThumbprint: computeThumbprint); /// Generates a public . - public static ECJwk GeneratePublicKey(in EllipticalCurve curve, bool computeThumbprint = true) + public static ECJwk GeneratePublicKey(EllipticalCurve curve, bool computeThumbprint = true) => GenerateKey(curve, withPrivateKey: false, computeThumbprint: computeThumbprint); /// Generates a public . @@ -411,11 +438,11 @@ public static ECJwk GeneratePublicKey(SignatureAlgorithm algorithm, bool compute => GenerateKey(algorithm, withPrivateKey: false, computeThumbprint: computeThumbprint); /// Generates a public . - public static ECJwk GeneratePublicKey(in EllipticalCurve curve, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) + public static ECJwk GeneratePublicKey(EllipticalCurve curve, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) => GenerateKey(curve, algorithm, withPrivateKey: false, computeThumbprint: computeThumbprint); /// Generates a . - private static ECJwk GenerateKey(in EllipticalCurve curve, bool withPrivateKey, bool computeThumbprint = true) + private static ECJwk GenerateKey(EllipticalCurve curve, bool withPrivateKey, bool computeThumbprint = true) { ECParameters parameters = GenerateParameters(curve, withPrivateKey); return FromParameters(parameters, computeThumbprint: computeThumbprint); @@ -452,13 +479,13 @@ private static ECJwk GenerateKey(SignatureAlgorithm algorithm, bool withPrivateK } /// Generates a . - private static ECJwk GenerateKey(in EllipticalCurve curve, KeyManagementAlgorithm algorithm, bool withPrivateKey, bool computeThumbprint = true) + private static ECJwk GenerateKey(EllipticalCurve curve, KeyManagementAlgorithm algorithm, bool withPrivateKey, bool computeThumbprint = true) { ECParameters parameters = GenerateParameters(curve, withPrivateKey); return FromParameters(parameters, algorithm, computeThumbprint: computeThumbprint); } - private static ECParameters GenerateParameters(in EllipticalCurve curve, bool withPrivateKey) + private static ECParameters GenerateParameters(EllipticalCurve curve, bool withPrivateKey) { using ECDsa ecdsa = ECDsa.Create(); ecdsa.GenerateKey(curve.CurveParameters); @@ -516,23 +543,23 @@ protected internal override void Canonicalize(Span buffer) /// protected internal override int GetCanonicalizeSize() { - Debug.Assert(35 == + Debug.Assert(Crv.CanonicalizeSize == StartCanonicalizeValue.Length + Middle1CanonicalizeValue.Length + Middle2CanonicalizeValue.Length - + EndCanonicalizeValue.Length); - return 35 - + Base64Url.GetArraySizeRequiredToEncode(Crv.Name.EncodedUtf8Bytes.Length) - + Base64Url.GetArraySizeRequiredToEncode(X!.Length) - + Base64Url.GetArraySizeRequiredToEncode(Y!.Length); + + EndCanonicalizeValue.Length + + Base64Url.GetArraySizeRequiredToEncode(X.Length) + + Base64Url.GetArraySizeRequiredToEncode(Y.Length) + + Crv.Name.EncodedUtf8Bytes.Length); + return Crv.CanonicalizeSize; } /// Returns a new instance of . - public static ECJwk FromParameters(ECParameters parameters, KeyManagementAlgorithm algorithm) + public static ECJwk FromParameters(in ECParameters parameters, KeyManagementAlgorithm algorithm) => FromParameters(parameters, algorithm, computeThumbprint: false); /// Returns a new instance of . - public static ECJwk FromParameters(ECParameters parameters, KeyManagementAlgorithm algorithm, bool computeThumbprint) + public static ECJwk FromParameters(in ECParameters parameters, KeyManagementAlgorithm algorithm, bool computeThumbprint) { var key = new ECJwk(parameters, algorithm); if (computeThumbprint) @@ -544,11 +571,11 @@ public static ECJwk FromParameters(ECParameters parameters, KeyManagementAlgorit } /// Returns a new instance of . - public static ECJwk FromParameters(ECParameters parameters, SignatureAlgorithm algorithm) + public static ECJwk FromParameters(in ECParameters parameters, SignatureAlgorithm algorithm) => FromParameters(parameters, algorithm, computeThumbprint: false); /// Returns a new instance of . - public static ECJwk FromParameters(ECParameters parameters, SignatureAlgorithm algorithm, bool computeThumbprint) + public static ECJwk FromParameters(in ECParameters parameters, SignatureAlgorithm algorithm, bool computeThumbprint) { var key = new ECJwk(parameters, algorithm); if (computeThumbprint) @@ -560,7 +587,7 @@ public static ECJwk FromParameters(ECParameters parameters, SignatureAlgorithm a } /// Returns a new instance of . - public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, string d, bool computeThumbprint = true) + public static ECJwk FromBase64Url(EllipticalCurve crv, string x, string y, string d, bool computeThumbprint = true) { var key = new ECJwk(crv, d: d, x: x, y: y); if (computeThumbprint) @@ -572,7 +599,7 @@ public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, st } /// Returns a new instance of . - public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, string d, SignatureAlgorithm alg, bool computeThumbprint = true) + public static ECJwk FromBase64Url(EllipticalCurve crv, string x, string y, string d, SignatureAlgorithm alg, bool computeThumbprint = true) { var key = new ECJwk(crv, d: d, x: x, y: y, alg: alg); if (computeThumbprint) @@ -584,7 +611,7 @@ public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, st } /// Returns a new instance of . - public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, string d, KeyManagementAlgorithm alg, bool computeThumbprint = true) + public static ECJwk FromBase64Url(EllipticalCurve crv, string x, string y, string d, KeyManagementAlgorithm alg, bool computeThumbprint = true) { var key = new ECJwk(crv, d: d, x: x, y: y, alg: alg); if (computeThumbprint) @@ -596,7 +623,7 @@ public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, st } /// Returns a new instance of . - public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, byte[] d, bool computeThumbprint = true) + public static ECJwk FromByteArray(EllipticalCurve crv, byte[] x, byte[] y, byte[] d, bool computeThumbprint = true) { var key = new ECJwk(crv, d: d, x: x, y: y); if (computeThumbprint) @@ -608,7 +635,7 @@ public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, by } /// Returns a new instance of . - public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, bool computeThumbprint = true) + public static ECJwk FromBase64Url(EllipticalCurve crv, string x, string y, bool computeThumbprint = true) { var key = new ECJwk(crv, x: x, y: y); if (computeThumbprint) @@ -620,7 +647,7 @@ public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, bo } /// Returns a new instance of . - public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, bool computeThumbprint = true) + public static ECJwk FromByteArray(EllipticalCurve crv, byte[] x, byte[] y, bool computeThumbprint = true) { var key = new ECJwk(crv, x: x, y: y); if (computeThumbprint) @@ -632,7 +659,7 @@ public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, bo } /// Returns a new instance of . - public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, byte[] d, SignatureAlgorithm algorithm, bool computeThumbprint = true) + public static ECJwk FromByteArray(EllipticalCurve crv, byte[] x, byte[] y, byte[] d, SignatureAlgorithm algorithm, bool computeThumbprint = true) { var key = new ECJwk(crv, d: d, x: x, y: y, algorithm); if (computeThumbprint) @@ -644,7 +671,7 @@ public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, by } /// Returns a new instance of . - public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, SignatureAlgorithm algorithm, bool computeThumbprint = true) + public static ECJwk FromBase64Url(EllipticalCurve crv, string x, string y, SignatureAlgorithm algorithm, bool computeThumbprint = true) { var key = new ECJwk(crv, x: x, y: y, algorithm); if (computeThumbprint) @@ -656,7 +683,7 @@ public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, Si } /// Returns a new instance of . - public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, SignatureAlgorithm algorithm, bool computeThumbprint = true) + public static ECJwk FromByteArray(EllipticalCurve crv, byte[] x, byte[] y, SignatureAlgorithm algorithm, bool computeThumbprint = true) { var key = new ECJwk(crv, x: x, y: y, algorithm); if (computeThumbprint) @@ -668,7 +695,7 @@ public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, Si } /// Returns a new instance of . - public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, byte[] d, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) + public static ECJwk FromByteArray(EllipticalCurve crv, byte[] x, byte[] y, byte[] d, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) { var key = new ECJwk(crv, d: d, x: x, y: y, algorithm); if (computeThumbprint) @@ -680,7 +707,7 @@ public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, by } /// Returns a new instance of . - public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) + public static ECJwk FromBase64Url(EllipticalCurve crv, string x, string y, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) { var key = new ECJwk(crv, x: x, y: y, algorithm); if (computeThumbprint) @@ -692,7 +719,7 @@ public static ECJwk FromBase64Url(in EllipticalCurve crv, string x, string y, Ke } /// Returns a new instance of . - public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) + public static ECJwk FromByteArray(EllipticalCurve crv, byte[] x, byte[] y, KeyManagementAlgorithm algorithm, bool computeThumbprint = true) { var key = new ECJwk(crv, x: x, y: y, algorithm); if (computeThumbprint) @@ -704,11 +731,11 @@ public static ECJwk FromByteArray(in EllipticalCurve crv, byte[] x, byte[] y, Ke } /// Returns a new instance of . - public static ECJwk FromParameters(ECParameters parameters) + public static ECJwk FromParameters(in ECParameters parameters) => FromParameters(parameters, computeThumbprint: false); /// Returns a new instance of . - public static ECJwk FromParameters(ECParameters parameters, bool computeThumbprint = true) + public static ECJwk FromParameters(in ECParameters parameters, bool computeThumbprint = true) { var key = new ECJwk(parameters); if (computeThumbprint) @@ -826,7 +853,9 @@ public override void WriteTo(Utf8JsonWriter writer) writer.WriteString(JwkParameterNames.Crv, Crv.Name); // X & Y & D have the same length - Span buffer = stackalloc byte[Base64Url.GetArraySizeRequiredToEncode(_parameters.Q.X!.Length)]; + const int ECParameterStackallocThreshold = 88; + Span buffer = stackalloc byte[ECParameterStackallocThreshold] + .Slice(0, Base64Url.GetArraySizeRequiredToEncode(_parameters.Q.X!.Length)); WriteBase64UrlProperty(writer, buffer, _parameters.Q.X!, JwkParameterNames.X); WriteBase64UrlProperty(writer, buffer, _parameters.Q.Y!, JwkParameterNames.Y); diff --git a/src/JsonWebToken/Internal/Constants.cs b/src/JsonWebToken/Internal/Constants.cs index 05d0e37f..ff5917a8 100644 --- a/src/JsonWebToken/Internal/Constants.cs +++ b/src/JsonWebToken/Internal/Constants.cs @@ -10,6 +10,7 @@ internal static class Constants internal const int JwsSegmentCount = 3; internal const int MaxStackallocBytes = 256; + internal const int MaxStackallocChars = MaxStackallocBytes / 2; internal const int DecompressionBufferLength = 1024; diff --git a/src/JsonWebToken/Internal/MemberNotNullAttribute.cs b/src/JsonWebToken/Internal/MemberNotNullAttribute.cs new file mode 100644 index 00000000..8554c539 --- /dev/null +++ b/src/JsonWebToken/Internal/MemberNotNullAttribute.cs @@ -0,0 +1,23 @@ +// Copyright (c) 2020 Yann Crumeyrolle. All rights reserved. +// Licensed under the MIT license. See LICENSE in the project root for license information. + +#if NETSTANDARD2_0 || NET461 || NET47 || NETCOREAPP2_1 || NETCOREAPP3_1 +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that the method or property will ensure that the listed field and property members have values that aren't null. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + public sealed class MemberNotNullAttribute : Attribute + { + /// Initializes the attribute with a field or property member. + /// The field or property member that is promised to be non-null. + public MemberNotNullAttribute(string member) => Members = new[] { member }; + + /// Initializes the attribute with the list of field and property members. + /// The list of field and property members that are promised to be non-null. + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } + } +} +#endif \ No newline at end of file diff --git a/src/JsonWebToken/Internal/ThrowHelper.cs b/src/JsonWebToken/Internal/ThrowHelper.cs index 0740b8ca..d80148a8 100644 --- a/src/JsonWebToken/Internal/ThrowHelper.cs +++ b/src/JsonWebToken/Internal/ThrowHelper.cs @@ -35,6 +35,11 @@ internal static bool TryWriteError(out int bytesWritten) [MethodImpl(MethodImplOptions.NoInlining)] private static Exception CreateArgumentNullException(ExceptionArgument argument) => new ArgumentNullException(GetArgumentName(argument)); + [DoesNotReturn] + internal static void ThrowArgumentNullException(ExceptionArgument argument, string message) => throw CreateArgumentNullException(argument, message); + [MethodImpl(MethodImplOptions.NoInlining)] + private static Exception CreateArgumentNullException(ExceptionArgument argument, string message) => new ArgumentNullException(GetArgumentName(argument), message); + [DoesNotReturn] internal static void ThrowInvalidOperationException_PolicyBuilderRequireSignature() => throw CreateInvalidOperationException_PolicyBuilderRequireSignature(); [MethodImpl(MethodImplOptions.NoInlining)] @@ -101,12 +106,12 @@ private static Exception CreateJwtDescriptorException_HeaderMustBeOfType(JsonEnc internal static void ThrowInvalidOperationException_ConcurrentOperationsNotSupported() => throw CreateInvalidOperationException_ConcurrentOperationsNotSupported(); [MethodImpl(MethodImplOptions.NoInlining)] private static Exception CreateInvalidOperationException_ConcurrentOperationsNotSupported() => new InvalidOperationException("Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct."); - + [DoesNotReturn] internal static void ThrowInvalidOperationException_AlreadyInitialized(ExceptionArgument argument) => throw CreateInvalidOperationException_AlreadyInitialized(argument); [MethodImpl(MethodImplOptions.NoInlining)] private static Exception CreateInvalidOperationException_AlreadyInitialized(ExceptionArgument argument) => new InvalidOperationException($"The property '{argument}' is already initialized. You cannot set more than once this property."); - + [DoesNotReturn] internal static void ThrowInvalidOperationException_NotInitialized(ExceptionArgument argument) => throw CreateInvalidOperationException_NotInitialized(argument); [MethodImpl(MethodImplOptions.NoInlining)] @@ -115,7 +120,7 @@ private static Exception CreateJwtDescriptorException_HeaderMustBeOfType(JsonEnc [DoesNotReturn] internal static void ThrowArgumentOutOfRangeException_MustBeGreaterOrEqualToZero(ExceptionArgument argument, int value) => throw CreateArgumentOutOfRangeException_MustBeGreaterOrEqualToZero(argument, value); [MethodImpl(MethodImplOptions.NoInlining)] - private static Exception CreateArgumentOutOfRangeException_MustBeGreaterOrEqualToZero(ExceptionArgument argument, int value) => new ArgumentOutOfRangeException(GetArgumentName(argument), $"{GetArgumentName(argument)} must be greater equal or zero. value: '{value}'."); + private static Exception CreateArgumentOutOfRangeException_MustBeGreaterOrEqualToZero(ExceptionArgument argument, int value) => new ArgumentOutOfRangeException(GetArgumentName(argument), $"{GetArgumentName(argument)} must be greater or equal to zero. value: '{value}'."); [DoesNotReturn] internal static void ThrowInvalidOperationException_NotSupportedJsonType(JwtValueKind type) => throw CreateInvalidOperationException_NotSupportedJsonType(type); @@ -143,6 +148,11 @@ private static Exception CreateJwtDescriptorException_HeaderMustBeOfType(JsonEnc [MethodImpl(MethodImplOptions.NoInlining)] private static Exception CreateCryptographicException_EncryptionFailed(EncryptionAlgorithm? algorithm, Jwk key, Exception innerException) => new CryptographicException($"Encryption failed for: Algorithm: '{algorithm}', key: '{key.Kid}'. See inner exception.", innerException); + [DoesNotReturn] + internal static void ThrowCryptographicException_SignatureFailed(SignatureAlgorithm? algorithm, Jwk key, Exception? innerException = null) => throw CreateCryptographicException_SignatureFailed(algorithm, key, innerException); + [MethodImpl(MethodImplOptions.NoInlining)] + private static Exception CreateCryptographicException_SignatureFailed(SignatureAlgorithm? algorithm, Jwk key, Exception? innerException) => innerException is null ? new CryptographicException($"Encryption failed for: Algorithm: '{algorithm}', key: '{key.Kid}'") : new CryptographicException($"Encryption failed for: Algorithm: '{algorithm}', key: '{key.Kid}'. See inner exception.", innerException); + [DoesNotReturn] internal static void ThrowJsonElementWrongType_InvalidOperationException(JsonTokenType expectedTokenType, JsonTokenType tokenType) => throw CreateJsonElementWrongType_InvalidOperationException(expectedTokenType, tokenType); [MethodImpl(MethodImplOptions.NoInlining)] @@ -157,7 +167,7 @@ private static Exception CreateJwtDescriptorException_HeaderMustBeOfType(JsonEnc internal static void ThrowNotSupportedException_AlgorithmForKeyWrap(KeyManagementAlgorithm? algorithm) => throw CreateNotSupportedException_AlgorithmForKeyWrap(algorithm); [MethodImpl(MethodImplOptions.NoInlining)] internal static Exception CreateNotSupportedException_AlgorithmForKeyWrap(KeyManagementAlgorithm? algorithm) => new NotSupportedException($"Key wrap is not supported for algorithm: '{algorithm}'."); - + [DoesNotReturn] internal static void ThrowNotSupportedException_AlgorithmForKeyWrap(AlgorithmId algorithm) => throw CreateNotSupportedException_AlgorithmForKeyWrap(algorithm); [MethodImpl(MethodImplOptions.NoInlining)] @@ -225,9 +235,9 @@ private static Exception CreateJwtDescriptorException_HeaderMustBeOfType(JsonEnc #if SUPPORT_ELLIPTIC_CURVE [DoesNotReturn] - internal static void ThrowNotSupportedException_SignatureAlgorithm(SignatureAlgorithm? algorithm, in EllipticalCurve curve) => throw CreateNotSupportedException_SignatureAlgorithm(algorithm, curve); + internal static void ThrowNotSupportedException_SignatureAlgorithm(SignatureAlgorithm? algorithm, EllipticalCurve curve) => throw CreateNotSupportedException_SignatureAlgorithm(algorithm, curve); [MethodImpl(MethodImplOptions.NoInlining)] - private static Exception CreateNotSupportedException_SignatureAlgorithm(SignatureAlgorithm? algorithm, in EllipticalCurve curve) => new NotSupportedException($"Signature failed. No support for: Algorithm: '{algorithm}' with curve '{curve}'."); + private static Exception CreateNotSupportedException_SignatureAlgorithm(SignatureAlgorithm? algorithm, EllipticalCurve curve) => new NotSupportedException($"Signature failed. No support for: Algorithm: '{algorithm}' with curve '{curve}'."); #endif [DoesNotReturn] @@ -330,6 +340,7 @@ private static string GetArgumentName(ExceptionArgument argument) case ExceptionArgument.d: return "d"; case ExceptionArgument.x: return "x"; case ExceptionArgument.y: return "y"; + case ExceptionArgument.crv: return "crv"; case ExceptionArgument.signatureFactory: return "signatureFactory"; case ExceptionArgument.keyWrapFactory: return "keyWrapFactory"; case ExceptionArgument.authenticatedEncryptionFactory: return "authenticatedEncryptionFactory"; @@ -353,6 +364,7 @@ private static string GetArgumentName(ExceptionArgument argument) case ExceptionArgument.signingKey: return "signingKey"; case ExceptionArgument.encryptionKey: return "encryptionKey"; case ExceptionArgument.payload: return "payload"; + case ExceptionArgument.parameters: return "parameters"; case ExceptionArgument.decryptionKeyProviders: return "decryptionKeyProviders"; case ExceptionArgument.signerFactory: return "signerFactory"; case ExceptionArgument.keyWrapperFactory: return "keyWrapperFactory"; @@ -373,6 +385,7 @@ private static string GetArgumentName(ExceptionArgument argument) case ExceptionArgument.count: return "count"; case ExceptionArgument.clockSkew: return "clockSkew"; case ExceptionArgument.size: return "size"; + case ExceptionArgument.saltSizeInBytes: return "saltSizeInBytes"; case ExceptionArgument.capacity: return "capacity"; case ExceptionArgument.base64: return "base64"; case ExceptionArgument.base64url: return "base64url"; @@ -399,6 +412,7 @@ internal enum ExceptionArgument d, x, y, + crv, signatureFactory, keyWrapFactory, authenticatedEncryptionFactory, @@ -422,6 +436,7 @@ internal enum ExceptionArgument signingKey, encryptionKey, payload, + parameters, decryptionKeyProviders, signerFactory, keyWrapperFactory, @@ -442,6 +457,7 @@ internal enum ExceptionArgument count, clockSkew, size, + saltSizeInBytes, capacity, base64, base64url, diff --git a/src/JsonWebToken/Jwk.cs b/src/JsonWebToken/Jwk.cs index 1b7a09b4..57059048 100644 --- a/src/JsonWebToken/Jwk.cs +++ b/src/JsonWebToken/Jwk.cs @@ -67,6 +67,8 @@ public abstract class Jwk : IDisposable, IEquatable private JsonEncodedText _use; private IList? _keyOps; private List? _x5c; + private byte[]? _x5t; + private byte[]? _x5tS256; /// Initializes a new instance of the class. protected Jwk() @@ -147,10 +149,34 @@ public List X5c } /// Gets or sets the 'x5t' (X.509 Certificate SHA-1 thumbprint). - public byte[]? X5t { get; set; } + public byte[]? X5t + { + get => _x5t; + set + { + if (value?.Length != 20) + { + throw new InvalidOperationException($"The parameter 'x5t' must be a byte array of 160 bits (20 bytes). Current size: {value?.Length * 8} bits."); + } + + _x5t = value; + } + } /// Gets or sets the 'x5t#S256' (X.509 Certificate SHA-256 thumbprint). - public byte[]? X5tS256 { get; set; } + public byte[]? X5tS256 + { + get => _x5tS256; + set + { + if (value?.Length != Sha256.Sha256HashSize) + { + throw new InvalidOperationException($"The parameter 'x5t#S256' must be a byte array of 256 bits (32 bytes). Current size: {value?.Length * 8} bits."); + } + + _x5tS256 = value; + } + } /// Gets or sets the 'x5u' (X.509 URL). public string? X5u { get; set; } @@ -601,8 +627,8 @@ public byte[] Canonicalize() try { Span buffer = size > Constants.MaxStackallocBytes - ? stackalloc byte[size] - : (arrayToReturn = ArrayPool.Shared.Rent(size)); + ? (arrayToReturn = ArrayPool.Shared.Rent(size)) + : stackalloc byte[size]; Canonicalize(buffer); return buffer.Slice(0, size).ToArray(); } @@ -639,10 +665,11 @@ public void ComputeThumbprint(Span destination) try { Span buffer = size > Constants.MaxStackallocBytes - ? stackalloc byte[size] - : (arrayToReturn = ArrayPool.Shared.Rent(size)); + ? (arrayToReturn = ArrayPool.Shared.Rent(size)) + : stackalloc byte[Constants.MaxStackallocBytes]; + buffer = buffer.Slice(0, size); Canonicalize(buffer); - Sha256.Shared.ComputeHash(buffer.Slice(0, size), hash); + Sha256.Shared.ComputeHash(buffer, hash); Base64Url.Encode(hash, destination); } finally @@ -800,9 +827,9 @@ public static Jwk FromJson(string json) try { int length = Utf8.GetMaxByteCount(json.Length); - Span jsonSpan = length <= Constants.MaxStackallocBytes - ? stackalloc byte[length] - : (jsonToReturn = ArrayPool.Shared.Rent(length)); + Span jsonSpan = length > Constants.MaxStackallocBytes + ? (jsonToReturn = ArrayPool.Shared.Rent(length)) + : stackalloc byte[Constants.MaxStackallocBytes]; length = Utf8.GetBytes(json, jsonSpan); jsonSpan = jsonSpan.Slice(0, length); var reader = new Utf8JsonReader(jsonSpan, true, default); @@ -1324,7 +1351,7 @@ internal static void PopulateEight(ref Utf8JsonReader reader, ref byte pProperty { if (IntegerMarshal.ReadUInt64(ref pPropertyName) == x5t_S256) { - key.X5tS256 = Base64Url.Decode(reader.ValueSpan); + key._x5tS256 = Base64Url.Decode(reader.ValueSpan); } } @@ -1396,7 +1423,7 @@ internal static void PopulateThree(ref Utf8JsonReader reader, ref byte propertyN key.Use = JsonEncodedText.Encode(reader.ValueSpan, JsonSerializationBehavior.JsonEncoder); break; case x5t: - key.X5t = Base64Url.Decode(reader.ValueSpan); + key._x5t = Base64Url.Decode(reader.ValueSpan); break; case x5u: key.X5u = reader.GetString(); @@ -1438,18 +1465,44 @@ public virtual void WriteTo(Utf8JsonWriter writer) writer.WriteEndArray(); } - if (X5t != null) + if (_x5t != null) { - Span buffer = stackalloc byte[Base64Url.GetArraySizeRequiredToEncode(X5t.Length)]; - Base64Url.Encode(X5t, buffer); - writer.WriteString(JwkParameterNames.X5t, buffer); + byte[]? array = null; + Span buffer = _x5t.Length == 20 + ? stackalloc byte[27] // 27 = Base64Url.GetArraySizeRequiredToEncode(20) + : ArrayPool.Shared.Rent(Base64Url.GetArraySizeRequiredToEncode(_x5t.Length)); + try + { + int bytesWritten = Base64Url.Encode(_x5t, buffer); + writer.WriteString(JwkParameterNames.X5t, buffer.Slice(0, bytesWritten)); + } + finally + { + if (array != null) + { + ArrayPool.Shared.Return(array); + } + } } - if (X5tS256 != null) + if (_x5tS256 != null) { - Span buffer = stackalloc byte[Base64Url.GetArraySizeRequiredToEncode(X5tS256.Length)]; - int bytesWritten = Base64Url.Encode(X5tS256, buffer); - writer.WriteString(JwkParameterNames.X5tS256, buffer.Slice(0, bytesWritten)); + byte[]? array = null; + Span buffer = _x5tS256.Length == 32 + ? stackalloc byte[43] // 43 = Base64Url.GetArraySizeRequiredToEncode(32) + : ArrayPool.Shared.Rent(Base64Url.GetArraySizeRequiredToEncode(_x5tS256.Length)); + try + { + int bytesWritten = Base64Url.Encode(_x5tS256, buffer); + writer.WriteString(JwkParameterNames.X5tS256, buffer.Slice(0, bytesWritten)); + } + finally + { + if (array != null) + { + ArrayPool.Shared.Return(array); + } + } } if (X5u != null) diff --git a/src/JsonWebToken/Jwks.cs b/src/JsonWebToken/Jwks.cs index 981f0b5e..e458caec 100644 --- a/src/JsonWebToken/Jwks.cs +++ b/src/JsonWebToken/Jwks.cs @@ -512,9 +512,9 @@ public static Jwks FromJson(string issuer, string json) try { int length = Utf8.GetMaxByteCount(json.Length); - Span jsonSpan = length <= Constants.MaxStackallocBytes - ? stackalloc byte[length] - : (jsonToReturn = ArrayPool.Shared.Rent(length)); + Span jsonSpan = length > Constants.MaxStackallocBytes + ? (jsonToReturn = ArrayPool.Shared.Rent(length)) + : stackalloc byte[Constants.MaxStackallocBytes]; length = Utf8.GetBytes(json, jsonSpan); return FromJson(issuer, jsonSpan.Slice(0, length)); } diff --git a/src/JsonWebToken/PasswordBasedJwk.cs b/src/JsonWebToken/PasswordBasedJwk.cs index ca152274..8fd6e8aa 100644 --- a/src/JsonWebToken/PasswordBasedJwk.cs +++ b/src/JsonWebToken/PasswordBasedJwk.cs @@ -54,7 +54,7 @@ internal byte[] ToArray() /// This should not be longer that 128 bytes, and at least 16 bytes for "PBES2-HS256+A128KW", /// 24 bytes for "PBES2-HS384+A192KW" and 32 bytes for "PBES2-HS512+A256KW" /// The number of iterations. Should be at least 1000. - /// The salt size, in bytes. Should be at least 8 bytes. + /// The salt size, in bytes. Should be at least 8 bytes. Value greater than 256 bytes is not supported. /// Defines whether the thubpring should be computed. public static PasswordBasedJwk FromPassphrase(string passphrase, uint iterationCount = 1000, uint saltSizeInBytes = 8, bool computeThumbprint = true) { @@ -62,7 +62,7 @@ public static PasswordBasedJwk FromPassphrase(string passphrase, uint iterationC { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.bytes); } - + var innerKey = SymmetricJwk.FromByteArray(Utf8.GetBytes(passphrase), computeThumbprint); return new PasswordBasedJwk(innerKey, iterationCount, saltSizeInBytes); } @@ -75,7 +75,7 @@ public static PasswordBasedJwk FromPassphrase(string passphrase, uint iterationC /// 24 bytes for "PBES2-HS384+A192KW" and 32 bytes for "PBES2-HS512+A256KW" /// The key encryption algorithm. It must be a PBES2 algorithm. /// The number of iterations. Should be at least 1000. - /// The salt size, in bytes. Should be at least 8 bytes. + /// The salt size, in bytes. Should be at least 8 bytes. Value greater than 256 bytes is not supported. /// Defines whether the thubpring should be computed. public static PasswordBasedJwk FromPassphrase(string passphrase, KeyManagementAlgorithm algorithm, uint iterationCount = 1000, uint saltSizeInBytes = 8, bool computeThumbprint = true) { diff --git a/src/JsonWebToken/Reader/JsonReaderHelper.cs b/src/JsonWebToken/Reader/JsonReaderHelper.cs index 56de649e..577f0026 100644 --- a/src/JsonWebToken/Reader/JsonReaderHelper.cs +++ b/src/JsonWebToken/Reader/JsonReaderHelper.cs @@ -182,22 +182,6 @@ public static bool IsInRangeInclusive(uint value, uint lowerBound, uint upperBou public static bool IsInRangeInclusive(int value, int lowerBound, int upperBound) => (uint)(value - lowerBound) <= (uint)(upperBound - lowerBound); - ///// - ///// Returns if is between - ///// and , inclusive. - ///// - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static bool IsInRangeInclusive(long value, long lowerBound, long upperBound) - // => (ulong)(value - lowerBound) <= (ulong)(upperBound - lowerBound); - - ///// - ///// Returns if is between - ///// and , inclusive. - ///// - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static bool IsInRangeInclusive(JsonTokenType value, JsonTokenType lowerBound, JsonTokenType upperBound) - // => (value - lowerBound) <= (upperBound - lowerBound); - public static bool UnescapeAndCompare(ReadOnlySpan utf8Source, ReadOnlySpan other) { Debug.Assert(utf8Source.Length >= other.Length && utf8Source.Length / JsonConstants.MaxExpansionFactorWhileEscaping <= other.Length); @@ -205,7 +189,7 @@ public static bool UnescapeAndCompare(ReadOnlySpan utf8Source, ReadOnlySpa byte[]? unescapedArray = null; Span utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocThreshold ? - stackalloc byte[utf8Source.Length] : + stackalloc byte[JsonConstants.StackallocThreshold] : (unescapedArray = ArrayPool.Shared.Rent(utf8Source.Length)); Unescape(utf8Source, utf8Unescaped, 0, out int written); @@ -233,7 +217,7 @@ public static string GetUnescapedString(ReadOnlySpan utf8Source, int idx) byte[]? pooledName = null; Span utf8Unescaped = length <= JsonConstants.StackallocThreshold ? - stackalloc byte[length] : + stackalloc byte[JsonConstants.StackallocThreshold] : (pooledName = ArrayPool.Shared.Rent(length)); Unescape(utf8Source, utf8Unescaped, idx, out int written); diff --git a/src/JsonWebToken/Reader/Jwt.cs b/src/JsonWebToken/Reader/Jwt.cs index 939dfc93..ef7d03f6 100644 --- a/src/JsonWebToken/Reader/Jwt.cs +++ b/src/JsonWebToken/Reader/Jwt.cs @@ -122,7 +122,7 @@ public static bool TryParse(string token, TokenValidationPolicy policy, out Jwt jwt = new Jwt(TokenValidationError.MalformedToken()); return false; } - + // Useful for b64? int length = Utf8.GetMaxByteCount(token.Length); if (length > policy.MaximumTokenSizeInBytes) { @@ -131,9 +131,9 @@ public static bool TryParse(string token, TokenValidationPolicy policy, out Jwt } byte[]? utf8ArrayToReturnToPool = null; - var utf8Token = length <= Constants.MaxStackallocBytes - ? stackalloc byte[length] - : (utf8ArrayToReturnToPool = ArrayPool.Shared.Rent(length)); + var utf8Token = length > Constants.MaxStackallocBytes + ? (utf8ArrayToReturnToPool = ArrayPool.Shared.Rent(length)) + : stackalloc byte[Constants.MaxStackallocBytes]; try { int bytesWritten = Utf8.GetBytes(token, utf8Token); @@ -509,9 +509,9 @@ private static bool TryDecryptToken( int headerLength = rawHeader.Length; int bufferLength = ciphertextLength + headerLength + initializationVectorLength + authenticationTagLength; byte[]? arrayToReturn = null; - Span buffer = bufferLength < Constants.MaxStackallocBytes - ? stackalloc byte[bufferLength] - : (arrayToReturn = ArrayPool.Shared.Rent(bufferLength)); + Span buffer = bufferLength > Constants.MaxStackallocBytes + ? (arrayToReturn = ArrayPool.Shared.Rent(bufferLength)) + : stackalloc byte[Constants.MaxStackallocBytes]; Span ciphertext = buffer.Slice(0, ciphertextLength); Span header = buffer.Slice(ciphertextLength, headerLength); @@ -526,10 +526,10 @@ private static bool TryDecryptToken( try { int utf8HeaderLength = Utf8.GetMaxCharCount(header.Length); - Span utf8Header = utf8HeaderLength < Constants.MaxStackallocBytes - ? stackalloc char[utf8HeaderLength] - : (headerArrayToReturn = ArrayPool.Shared.Rent(utf8HeaderLength)); - + Span utf8Header = utf8HeaderLength > Constants.MaxStackallocChars + ? (headerArrayToReturn = ArrayPool.Shared.Rent(utf8HeaderLength)) + : stackalloc char[Constants.MaxStackallocChars]; + utf8HeaderLength = Utf8.GetChars(rawHeader, utf8Header); Ascii.GetBytes(utf8Header.Slice(0, utf8HeaderLength), header); } @@ -592,10 +592,10 @@ private static bool TryGetContentEncryptionKeys(JwtHeaderDocument header, ReadOn int decodedSize = Base64Url.GetArraySizeRequiredToDecode(rawEncryptedKey.Length); byte[]? encryptedKeyToReturnToPool = null; - byte[]? unwrappedKeyToReturnToPool = null; - Span encryptedKey = decodedSize <= Constants.MaxStackallocBytes ? - stackalloc byte[decodedSize] : - encryptedKeyToReturnToPool = ArrayPool.Shared.Rent(decodedSize); + const int KeySizeThreshold = 72; + Span encryptedKey = decodedSize > KeySizeThreshold + ? encryptedKeyToReturnToPool = ArrayPool.Shared.Rent(decodedSize) + : stackalloc byte[KeySizeThreshold]; try { @@ -626,9 +626,8 @@ private static bool TryGetContentEncryptionKeys(JwtHeaderDocument header, ReadOn } keys = new List(1); - Span unwrappedKey = maxKeyUnwrapSize <= Constants.MaxStackallocBytes ? - stackalloc byte[maxKeyUnwrapSize] : - unwrappedKeyToReturnToPool = ArrayPool.Shared.Rent(maxKeyUnwrapSize); + const int UnwrappedKeySizeThreshold = 64; + Span unwrappedKey = stackalloc byte[UnwrappedKeySizeThreshold]; for (int i = 0; i < keyUnwrappers.Count; i++) { var kpv = keyUnwrappers[i]; @@ -646,11 +645,6 @@ private static bool TryGetContentEncryptionKeys(JwtHeaderDocument header, ReadOn { ArrayPool.Shared.Return(encryptedKeyToReturnToPool, true); } - - if (unwrappedKeyToReturnToPool != null) - { - ArrayPool.Shared.Return(unwrappedKeyToReturnToPool, true); - } } } else diff --git a/src/JsonWebToken/Reader/SignatureValidationPolicy.cs b/src/JsonWebToken/Reader/SignatureValidationPolicy.cs index 75a23b74..2b0736e1 100644 --- a/src/JsonWebToken/Reader/SignatureValidationPolicy.cs +++ b/src/JsonWebToken/Reader/SignatureValidationPolicy.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using JsonWebToken.Cryptography; namespace JsonWebToken { @@ -109,7 +110,7 @@ public override bool TryValidateSignature(JwtHeaderDocument header, JwtPayloadDo } error = SignatureValidationError.InvalidSignature(); - return false; + return false; } } @@ -130,31 +131,35 @@ public override bool TryValidateSignature(JwtHeaderDocument header, JwtPayloadDo { if (signatureSegment.IsEmpty) { - if(contentBytes.IsEmpty) - { - // This is not a JWS + if (contentBytes.IsEmpty) + { + // This is not a JWS goto Success; - } - else - { + } + else + { error = SignatureValidationError.MissingSignature(); - goto Error; - } + goto Error; + } } + byte[]? signatureToReturn = null; try { int signatureBytesLength = Base64Url.GetArraySizeRequiredToDecode(signatureSegment.Length); - Span signatureBytes = stackalloc byte[signatureBytesLength]; + Span signatureBytes = signatureBytesLength > Constants.MaxStackallocBytes + ? (signatureToReturn = ArrayPool.Shared.Rent(signatureBytesLength)) + : stackalloc byte[Constants.MaxStackallocBytes]; + if (Base64Url.Decode(signatureSegment, signatureBytes, out _, out int bytesWritten) != OperationStatus.Done) { error = SignatureValidationError.MalformedSignature(); - goto Error; + goto Error; } - Debug.Assert(bytesWritten == signatureBytes.Length); + signatureBytes = signatureBytes.Slice(0, bytesWritten); + //Debug.Assert(signatureBytesLength == bytesWritten); bool keysTried = false; - var keySet = _keyProvider.GetKeys(header); var algElement = header.Alg; if (keySet != null) @@ -170,9 +175,9 @@ public override bool TryValidateSignature(JwtHeaderDocument header, JwtPayloadDo { if (key.TryGetSignatureVerifier(alg, out var signatureVerifier)) { - if (signatureVerifier.Verify(contentBytes, signatureBytes)) + if (signatureVerifier.Verify(contentBytes, signatureBytes.Slice(0, signatureBytesLength))) { - goto Success; + goto Success; } } } @@ -190,13 +195,20 @@ public override bool TryValidateSignature(JwtHeaderDocument header, JwtPayloadDo { error = SignatureValidationError.MalformedSignature(e); } - - Error: - return false; - - Success: - error = null; - return true; + finally + { + if (signatureToReturn != null) + { + ArrayPool.Shared.Return(signatureToReturn); + } + } + + Error: + return false; + + Success: + error = null; + return true; } } @@ -204,17 +216,17 @@ private sealed class NoSignatureValidationPolicy : SignatureValidationPolicy { public override bool IsEnabled => true; - public override bool TryValidateSignature(JwtHeaderDocument header, JwtPayloadDocument payload, ReadOnlySpan contentBytes, ReadOnlySpan signatureSegment, [NotNullWhen(false)] out SignatureValidationError? error ) + public override bool TryValidateSignature(JwtHeaderDocument header, JwtPayloadDocument payload, ReadOnlySpan contentBytes, ReadOnlySpan signatureSegment, [NotNullWhen(false)] out SignatureValidationError? error) { - if((contentBytes.Length == 0 && signatureSegment.Length == 0) + if ((contentBytes.Length == 0 && signatureSegment.Length == 0) || (signatureSegment.IsEmpty && !header.Alg.IsEmpty && header.Alg.ValueEquals(SignatureAlgorithm.None.Utf8Name))) - { - error = null; - return true; - } + { + error = null; + return true; + } error = SignatureValidationError.InvalidSignature(); - return false; + return false; } } @@ -225,7 +237,7 @@ private sealed class IgnoreSignatureValidationPolicy : SignatureValidationPolicy public override bool TryValidateSignature(JwtHeaderDocument header, JwtPayloadDocument payload, ReadOnlySpan contentBytes, ReadOnlySpan signatureSegment, [NotNullWhen(false)] out SignatureValidationError? error) { error = null; - return true; + return true; } } @@ -236,7 +248,7 @@ private sealed class InvalidSignatureValidationPolicy : SignatureValidationPolic public override bool TryValidateSignature(JwtHeaderDocument header, JwtPayloadDocument payload, ReadOnlySpan contentBytes, ReadOnlySpan signatureSegment, [NotNullWhen(false)] out SignatureValidationError? error) { error = SignatureValidationError.InvalidSignature(); - return false; + return false; } } } diff --git a/src/JsonWebToken/Reader/TokenValidationError.cs b/src/JsonWebToken/Reader/TokenValidationError.cs index d23a7c68..ca41739a 100644 --- a/src/JsonWebToken/Reader/TokenValidationError.cs +++ b/src/JsonWebToken/Reader/TokenValidationError.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Text; namespace JsonWebToken { @@ -53,6 +54,33 @@ private TokenValidationError(TokenValidationStatus status, string message) _message = message; } + /// + public override string ToString() + { + var builder = new StringBuilder(); + builder.Append("Status: ").AppendLine(Status.ToString()); + if (Exception is not null) + { + builder.Append("Exception: ").AppendLine(Exception.ToString()); + } + else if(Message is not null) + { + builder.Append("Message: ").AppendLine(Message); + } + + if (ErrorHeader is not null) + { + builder.Append("Error Header: ").AppendLine(ErrorHeader); + } + + if (ErrorClaim is not null) + { + builder.Append("Error Claim: ").AppendLine(ErrorClaim); + } + + return builder.ToString(); + } + /// The token has no error. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static TokenValidationError NoError() diff --git a/src/JsonWebToken/RsaJwk.cs b/src/JsonWebToken/RsaJwk.cs index d79b5537..3e8b7380 100644 --- a/src/JsonWebToken/RsaJwk.cs +++ b/src/JsonWebToken/RsaJwk.cs @@ -27,38 +27,37 @@ public sealed class RsaJwk : AsymmetricJwk private RSAParameters _parameters; -#nullable disable /// Initializes a new instance of . - private RsaJwk(RSAParameters rsaParameters) + private RsaJwk(in RSAParameters parameters) { - Verify(rsaParameters); - _parameters = rsaParameters; + Verify(parameters); + _parameters = parameters; } /// Initializes a new instance of . - private RsaJwk(RSAParameters rsaParameters, KeyManagementAlgorithm alg) + private RsaJwk(in RSAParameters parameters, KeyManagementAlgorithm alg) : base(alg) { - Verify(rsaParameters); + Verify(parameters); if (!SupportKeyManagement(alg)) { ThrowHelper.ThrowNotSupportedException_Algorithm(alg); } - _parameters = rsaParameters; + _parameters = parameters; } /// Initializes a new instance of . - private RsaJwk(RSAParameters rsaParameters, SignatureAlgorithm alg) + private RsaJwk(in RSAParameters parameters, SignatureAlgorithm alg) : base(alg) { - Verify(rsaParameters); + Verify(parameters); if (!SupportSignature(alg)) { ThrowHelper.ThrowNotSupportedException_Algorithm(alg); } - _parameters = rsaParameters; + _parameters = parameters; } /// Initializes a new instance of . @@ -229,7 +228,6 @@ private RsaJwk( private RsaJwk() { } -#nullable enable private void Initialize(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q, byte[] dp, byte[] dq, byte[] qi) { @@ -335,16 +333,16 @@ private void Initialize(string n, string e, string d, string p, string q, string _parameters.Exponent = Base64Url.Decode(e); } - private static void Verify(RSAParameters rsaParameters) + private static void Verify(in RSAParameters parameters) { - if (rsaParameters.Modulus is null) + if (parameters.Modulus is null) { - throw new ArgumentNullException(nameof(rsaParameters), $"The property '{nameof(rsaParameters.Modulus)}' cannot be null."); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.parameters, $"The property '{nameof(parameters.Modulus)}' cannot be null."); } - if (rsaParameters.Exponent is null) + if (parameters.Exponent is null) { - throw new ArgumentNullException(nameof(rsaParameters), $"The property '{nameof(rsaParameters.Exponent)}' cannot be null."); + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.parameters, $"The property '{nameof(parameters.Exponent)}' cannot be null."); } } @@ -517,9 +515,7 @@ private static RsaJwk GenerateKey(int sizeInBits, bool withPrivateKey, bool comp rsa.KeySize= sizeInBits; #endif #endif - RSAParameters rsaParameters = rsa.ExportParameters(withPrivateKey); - - return FromParameters(rsaParameters, computeThumbprint); + return FromParameters(rsa.ExportParameters(withPrivateKey), computeThumbprint); } /// Generates a new random . @@ -813,7 +809,7 @@ public static RsaJwk FromBase64Url( /// A that contains the key parameters. /// The /// Defines whether the thumbprint of the key should be computed - public static RsaJwk FromParameters(RSAParameters parameters, KeyManagementAlgorithm alg, bool computeThumbprint = true) + public static RsaJwk FromParameters(in RSAParameters parameters, KeyManagementAlgorithm alg, bool computeThumbprint = true) { var key = new RsaJwk(parameters, alg); if (computeThumbprint) @@ -828,7 +824,7 @@ public static RsaJwk FromParameters(RSAParameters parameters, KeyManagementAlgor /// A that contains the key parameters. /// The /// Defines whether the thumbprint of the key should be computed - public static RsaJwk FromParameters(RSAParameters parameters, SignatureAlgorithm alg, bool computeThumbprint) + public static RsaJwk FromParameters(in RSAParameters parameters, SignatureAlgorithm alg, bool computeThumbprint) { var key = new RsaJwk(parameters, alg); if (computeThumbprint) @@ -842,7 +838,7 @@ public static RsaJwk FromParameters(RSAParameters parameters, SignatureAlgorithm /// Returns a new instance of . /// A that contains the key parameters. /// Defines whether the thumbprint of the key should be computed - public static RsaJwk FromParameters(RSAParameters parameters, bool computeThumbprint = true) + public static RsaJwk FromParameters(in RSAParameters parameters, bool computeThumbprint = true) { var key = new RsaJwk(parameters); if (computeThumbprint) @@ -1006,13 +1002,10 @@ public override void WriteTo(Utf8JsonWriter writer) // the modulus N is always the biggest field int requiredBufferSize = Base64Url.GetArraySizeRequiredToEncode(_parameters.Modulus!.Length); - byte[]? arrayToReturn = null; + byte[] arrayToReturn; + Span buffer = arrayToReturn = ArrayPool.Shared.Rent(requiredBufferSize); try { - Span buffer = requiredBufferSize > Constants.MaxStackallocBytes - ? stackalloc byte[requiredBufferSize] - : (arrayToReturn = ArrayPool.Shared.Rent(requiredBufferSize)); - WriteBase64UrlProperty(writer, buffer, _parameters.Exponent!, JwkParameterNames.E); WriteBase64UrlProperty(writer, buffer, _parameters.Modulus!, JwkParameterNames.N); @@ -1025,10 +1018,7 @@ public override void WriteTo(Utf8JsonWriter writer) } finally { - if (arrayToReturn != null) - { - ArrayPool.Shared.Return(arrayToReturn); - } + ArrayPool.Shared.Return(arrayToReturn); } writer.WriteEndObject(); diff --git a/src/JsonWebToken/SymmetricJwk.cs b/src/JsonWebToken/SymmetricJwk.cs index b7426851..dcc94487 100644 --- a/src/JsonWebToken/SymmetricJwk.cs +++ b/src/JsonWebToken/SymmetricJwk.cs @@ -469,7 +469,7 @@ public override void WriteTo(Utf8JsonWriter writer) try { Span buffer = requiredBufferSize > Constants.MaxStackallocBytes - ? stackalloc byte[requiredBufferSize] + ? stackalloc byte[Constants.MaxStackallocBytes] : (arrayToReturn = ArrayPool.Shared.Rent(requiredBufferSize)); WriteBase64UrlProperty(writer, buffer, _k, JwkParameterNames.K); } diff --git a/src/JsonWebToken/Writer/JweDescriptor`1.cs b/src/JsonWebToken/Writer/JweDescriptor`1.cs index 3884a7a0..704d124c 100644 --- a/src/JsonWebToken/Writer/JweDescriptor`1.cs +++ b/src/JsonWebToken/Writer/JweDescriptor`1.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Security.Cryptography; using System.Text.Json; +using JsonWebToken.Cryptography; namespace JsonWebToken { @@ -156,9 +157,10 @@ protected void EncryptToken(ReadOnlySpan payload, EncodingContext context) byte[]? buffer64HeaderToReturnToPool = null; byte[]? arrayCiphertextToReturnToPool = null; int keyWrapSize = keyWrapper.GetKeyWrapSize(); - Span wrappedKey = keyWrapSize <= Constants.MaxStackallocBytes ? - stackalloc byte[keyWrapSize] : - new Span(wrappedKeyToReturnToPool = ArrayPool.Shared.Rent(keyWrapSize), 0, keyWrapSize); + Span wrappedKey = keyWrapSize > KeyWrapper.WrappedKeySizeStackallocThreshold ? + wrappedKeyToReturnToPool = ArrayPool.Shared.Rent(keyWrapSize) : + stackalloc byte[KeyWrapper.WrappedKeySizeStackallocThreshold]; + wrappedKey = wrappedKey.Slice(0, keyWrapSize); var cek = keyWrapper.WrapKey(null, header, wrappedKey); try @@ -182,9 +184,9 @@ protected void EncryptToken(ReadOnlySpan payload, EncodingContext context) } Span base64EncodedHeader = base64EncodedHeaderLength > Constants.MaxStackallocBytes - ? (buffer64HeaderToReturnToPool = ArrayPool.Shared.Rent(base64EncodedHeaderLength)).AsSpan(0, base64EncodedHeaderLength) - : stackalloc byte[base64EncodedHeaderLength]; - + ? (buffer64HeaderToReturnToPool = ArrayPool.Shared.Rent(base64EncodedHeaderLength)) + : stackalloc byte[Constants.MaxStackallocBytes]; + base64EncodedHeader = base64EncodedHeader.Slice(0, base64EncodedHeaderLength); int offset; if (cachedHeader != null) { @@ -218,7 +220,7 @@ protected void EncryptToken(ReadOnlySpan payload, EncodingContext context) int bufferSize = ciphertextSize + tagSize; Span buffer = bufferSize > Constants.MaxStackallocBytes ? (arrayCiphertextToReturnToPool = ArrayPool.Shared.Rent(bufferSize)) - : stackalloc byte[bufferSize]; + : stackalloc byte[Constants.MaxStackallocBytes]; Span tag = buffer.Slice(ciphertextSize, tagSize); Span ciphertext = buffer.Slice(0, ciphertextSize); @@ -226,7 +228,8 @@ protected void EncryptToken(ReadOnlySpan payload, EncodingContext context) var nonce = new byte[encryptor.GetNonceSize()]; _randomNumberGenerator.GetBytes(nonce); #else - Span nonce = stackalloc byte[encryptor.GetNonceSize()]; + int nonceSize = encryptor.GetNonceSize(); + Span nonce = stackalloc byte[AuthenticatedEncryptor.NonceSizeStackallocThreshold].Slice(0, nonceSize); RandomNumberGenerator.Fill(nonce); #endif encryptor.Encrypt(cek.K, payload, nonce, base64EncodedHeader.Slice(0, offset), ciphertext, tag, out int tagBytesWritten); @@ -253,7 +256,7 @@ protected void EncryptToken(ReadOnlySpan payload, EncodingContext context) encryptedToken[bytesWritten++] = Constants.ByteDot; bytesWritten += Base64Url.Encode(tag.Slice(0, tagBytesWritten), encryptedToken.Slice(bytesWritten)); - Debug.Assert(encryptionLength == bytesWritten); + // Debug.Assert(encryptionLength == bytesWritten); output.Advance(encryptionLength); } finally diff --git a/src/JsonWebToken/Writer/JwsDescriptor.cs b/src/JsonWebToken/Writer/JwsDescriptor.cs index d055f611..ba128400 100644 --- a/src/JsonWebToken/Writer/JwsDescriptor.cs +++ b/src/JsonWebToken/Writer/JwsDescriptor.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Diagnostics; using System.Text.Json; +using JsonWebToken.Cryptography; namespace JsonWebToken { @@ -147,10 +148,27 @@ public override void Encode(EncodingContext context) buffer[offset++] = Constants.ByteDot; offset += Base64Url.Encode(bufferWriter.WrittenSpan.Slice(0, payloadLength), buffer.Slice(offset)); buffer[offset] = Constants.ByteDot; - Span signature = stackalloc byte[signer.HashSizeInBytes]; - bool success = signer.TrySign(buffer.Slice(0, offset++), signature, out int signatureBytesWritten); - Debug.Assert(success); - Debug.Assert(signature.Length == signatureBytesWritten); + byte[]? signatureArray = null; + Span signature = signer.HashSizeInBytes > Signer.SignatureStackallocThreshold + ? (signatureArray = ArrayPool.Shared.Rent(signer.HashSizeInBytes)) + : stackalloc byte[Signer.SignatureStackallocThreshold]; + signature = signature.Slice(0, signer.HashSizeInBytes); + try + { + if(! signer.TrySign(buffer.Slice(0, offset++), signature, out int signatureBytesWritten)) + { + ThrowHelper.ThrowCryptographicException_SignatureFailed(alg, _signingKey); + } + + Debug.Assert(signature.Length == signatureBytesWritten); + } + finally + { + if (signatureArray != null) + { + ArrayPool.Shared.Return(signatureArray); + } + } int bytesWritten = Base64Url.Encode(signature, buffer.Slice(offset)); diff --git a/src/JsonWebToken/Writer/JwtWriter.cs b/src/JsonWebToken/Writer/JwtWriter.cs index 6b42c0d2..3a9bd0c9 100644 --- a/src/JsonWebToken/Writer/JwtWriter.cs +++ b/src/JsonWebToken/Writer/JwtWriter.cs @@ -89,7 +89,7 @@ public void WriteToken(JwtDescriptor descriptor, IBufferWriter output) /// Writes a JWT in its compact serialization format and returns it a string. /// The descriptor of the JWT. - /// The retpresention of the JWT. + /// The represention of the JWT. public string WriteTokenString(JwtDescriptor descriptor) { using var bufferWriter = new PooledByteBufferWriter(); diff --git a/src/JsonWebToken/Writer/PlaintextJweDescriptor.cs b/src/JsonWebToken/Writer/PlaintextJweDescriptor.cs index 480019fc..324f91d4 100644 --- a/src/JsonWebToken/Writer/PlaintextJweDescriptor.cs +++ b/src/JsonWebToken/Writer/PlaintextJweDescriptor.cs @@ -41,7 +41,7 @@ public override void Encode(EncodingContext context) byte[]? payloadToReturnToPool = null; Span encodedPayload = payloadLength > Constants.MaxStackallocBytes ? (payloadToReturnToPool = ArrayPool.Shared.Rent(payloadLength)) - : stackalloc byte[payloadLength]; + : stackalloc byte[Constants.MaxStackallocBytes]; try { diff --git a/test/JsonWebToken.Tests/DirectKeyWrapTests.cs b/test/JsonWebToken.Tests/DirectKeyWrapTests.cs index 5f45bfa0..f679f12a 100644 --- a/test/JsonWebToken.Tests/DirectKeyWrapTests.cs +++ b/test/JsonWebToken.Tests/DirectKeyWrapTests.cs @@ -25,7 +25,6 @@ public void Unwrap() { var staticKey = new byte[] { 111, 27, 25, 52, 66, 29, 20, 78, 92, 176, 56, 240, 65, 208, 82, 112, 161, 131, 36, 55, 202, 236, 185, 172, 129, 23, 153, 194, 195, 48, 253, 182 }; - var parsed = JwtHeaderDocument.TryParseHeader(Encoding.UTF8.GetBytes($"{{}}"), null, TokenValidationPolicy.NoValidation, out var jwtHeader, out var error); Assert.True(parsed); diff --git a/test/JsonWebToken.Tests/ECJwkTests.cs b/test/JsonWebToken.Tests/ECJwkTests.cs index 512e1053..616942ee 100644 --- a/test/JsonWebToken.Tests/ECJwkTests.cs +++ b/test/JsonWebToken.Tests/ECJwkTests.cs @@ -151,8 +151,8 @@ public void FromJson(string crvName, string json) } [Theory] - [InlineData("{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\",\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"use\":\"enc\",\"kid\":\"1\",\"x5c\":[\"MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==\"],\"x5t\":\"dGhpcyBpcyBhIFNIQTEgdGVzdCE\",\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA\",\"key_ops\":[\"sign\"],\"x5u\":\"https://example.com\"}")] - [InlineData("{\"crv\":\"P-256\",\"kty\":\"EC\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\",\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"use\":\"enc\",\"kid\":\"1\",\"x5c\":[\"MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==\"],\"x5t\":\"dGhpcyBpcyBhIFNIQTEgdGVzdCE\",\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA\",\"key_ops\":[\"sign\"],\"x5u\":\"https://example.com\"}")] + [InlineData("{\"kty\":\"EC\",\"crv\":\"P-256\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\",\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"use\":\"enc\",\"kid\":\"1\",\"x5c\":[\"MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==\"],\"x5t\":\"dGhpcyBpcyBhIFNIQTEgdGVzdCE\",\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA\",\"key_ops\":[\"sign\"],\"x5u\":\"https://example.com\"}")] + [InlineData("{\"crv\":\"P-256\",\"kty\":\"EC\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\",\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\",\"use\":\"enc\",\"kid\":\"1\",\"x5c\":[\"MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==\"],\"x5t\":\"dGhpcyBpcyBhIFNIQTEgdGVzdCE\",\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA\",\"key_ops\":[\"sign\"],\"x5u\":\"https://example.com\"}")] public override void FromJson_WithProperties(string json) { var key = Jwk.FromJson(json); @@ -164,7 +164,7 @@ public override void FromJson_WithProperties(string json) Assert.NotEmpty(jwk.X5c); Assert.Equal(Base64Url.Decode("dGhpcyBpcyBhIFNIQTEgdGVzdCE"), jwk.X5t); - Assert.Equal(Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA"), jwk.X5tS256); + Assert.Equal(Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA"), jwk.X5tS256); Assert.Equal(JwkKeyOpsValues.Sign, jwk.KeyOps[0]); Assert.Equal("https://example.com", jwk.X5u); } @@ -177,7 +177,7 @@ public override void WriteTo() key.KeyOps.Add(JwkKeyOpsValues.Sign); key.Use = JwkUseValues.Sig; key.X5t = Base64Url.Decode("dGhpcyBpcyBhIFNIQTEgdGVzdCE"); - key.X5tS256 = Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA"); + key.X5tS256 = Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA"); key.X5u = "https://example.com"; key.X5c.Add(Convert.FromBase64String("MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==")); @@ -189,7 +189,7 @@ public override void WriteTo() Assert.Contains("\"key_ops\":[\"sign\"]", json); Assert.Contains("\"use\":\"sig\"", json); Assert.Contains("\"x5t\":\"dGhpcyBpcyBhIFNIQTEgdGVzdCE\"", json); - Assert.Contains("\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA\"", json); + Assert.Contains("\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA\"", json); #if NETSTANDARD2_0 Assert.Contains("\"x5u\":\"" + JsonEncodedText.Encode("https://example.com") + "\"", json); Assert.Contains("\"x5c\":[\"MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K\u002bIiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel\u002bW1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW\u002boyVVkaZdklLQp2Btgt9qr21m42f4wTw\u002bXrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL\u002b9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo\u002bOwb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk\u002bfbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C\u002b2qok\u002b2\u002bqDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR\u002bN5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==\"]", json); @@ -370,14 +370,6 @@ internal static ECParameters GetNistP256ReferenceKey(bool includePrivateKey = tr d: "VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw" ); - private static ECJwk PrivateEcc256KKey => ECJwk.FromBase64Url - ( - crv: EllipticalCurve.Secp256k1, - x: "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ", - y: "e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck", - d: "VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw" - ); - private static ECJwk PublicEcc256Key => ECJwk.FromBase64Url ( crv: EllipticalCurve.P256, @@ -385,12 +377,19 @@ internal static ECParameters GetNistP256ReferenceKey(bool includePrivateKey = tr y: "e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck" ); + private static ECJwk PrivateEcc256KKey = ECJwk.FromBase64Url + ( + crv: EllipticalCurve.Secp256k1, + x: "6_H-LRU19Rzm4KCJNmzeCGoHPrm1CSBgOp-Npbdjaw0", + y: "tp7FPpiX9sAMyGr72y27afvfZxmlANjyRut9StOq9xk", + d: "Lra8VqtHiyayZ371elNxSJQg4OrWO0dLvMLiDfIRfc0" + ); - private static ECJwk PublicEcc256KKey => ECJwk.FromBase64Url + private static ECJwk PublicEcc256KKey = ECJwk.FromBase64Url ( crv: EllipticalCurve.Secp256k1, - x: "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ", - y: "e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck" + x: "6_H-LRU19Rzm4KCJNmzeCGoHPrm1CSBgOp-Npbdjaw0", + y: "tp7FPpiX9sAMyGr72y27afvfZxmlANjyRut9StOq9xk" ); private static ECJwk PublicEcc384Key => ECJwk.FromBase64Url diff --git a/test/JsonWebToken.Tests/JsonWebToken.Tests.csproj b/test/JsonWebToken.Tests/JsonWebToken.Tests.csproj index 5b6c3206..83f1bb71 100644 --- a/test/JsonWebToken.Tests/JsonWebToken.Tests.csproj +++ b/test/JsonWebToken.Tests/JsonWebToken.Tests.csproj @@ -66,5 +66,9 @@ $(DefineConstants);SUPPORT_ELLIPTIC_CURVE;SUPPORT_ELLIPTIC_CURVE_SIGNATURE;SUPPORT_ELLIPTIC_CURVE_KEYWRAPPING;SUPPORT_SIMD;SUPPORT_AESGCM;SUPPORT_JAVASCRIPT_ENCODER;SUPPORT_SPAN_CRYPTO + + + $(DefineConstants);TARGET_MACOS + diff --git a/test/JsonWebToken.Tests/JwkTestsBase.cs b/test/JsonWebToken.Tests/JwkTestsBase.cs index 2f0480b7..02f1c18b 100644 --- a/test/JsonWebToken.Tests/JwkTestsBase.cs +++ b/test/JsonWebToken.Tests/JwkTestsBase.cs @@ -54,8 +54,8 @@ public Jwk CanonicalizeKey(Jwk key) key.Kid = JsonEncodedText.Encode( "kid"); key.Use = JwkUseValues.Sig; key.X5c.Add(new byte[0]); - key.X5t = Encoding.UTF8.GetBytes("x5t"); - key.X5tS256 = Encoding.UTF8.GetBytes("x5t#256"); + key.X5t = Base64Url.Decode("XOf1YEg_zFLX0PtGjiEVvjM1WsA"); + key.X5tS256 = Base64Url.Decode("ZgPMqAT8BELhXwBa2nIT0OvdWtQCiF_g09nAyHhgCe0"); key.X5u = "https://example.com/jwks"; var json = key.Canonicalize(); var canonicalizedKey = Jwk.FromJson(Encoding.UTF8.GetString(json)); diff --git a/test/JsonWebToken.Tests/JwsTokenTests.cs b/test/JsonWebToken.Tests/JwsTokenTests.cs index adcef332..560a615a 100644 --- a/test/JsonWebToken.Tests/JwsTokenTests.cs +++ b/test/JsonWebToken.Tests/JwsTokenTests.cs @@ -26,7 +26,7 @@ public class JwsTokenTests n: "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", e: "AQAB" ); -#if !NET461 +#if SUPPORT_ELLIPTIC_CURVE_SIGNATURE private readonly ECJwk _privateEcc256Key = ECJwk.FromBase64Url ( crv: EllipticalCurve.P256, @@ -42,18 +42,32 @@ public class JwsTokenTests y: "e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck" ); - private readonly ECJwk _publicEcc384Key = ECJwk.FromBase64Url + private readonly ECJwk _privateEcc256KKey = ECJwk.FromBase64Url + ( + crv: EllipticalCurve.Secp256k1, + x: "6_H-LRU19Rzm4KCJNmzeCGoHPrm1CSBgOp-Npbdjaw0", + y: "tp7FPpiX9sAMyGr72y27afvfZxmlANjyRut9StOq9xk", + d: "Lra8VqtHiyayZ371elNxSJQg4OrWO0dLvMLiDfIRfc0" + ); + + private readonly ECJwk _publicEcc256KKey = ECJwk.FromBase64Url + ( + crv: EllipticalCurve.Secp256k1, + x: "6_H-LRU19Rzm4KCJNmzeCGoHPrm1CSBgOp-Npbdjaw0", + y: "tp7FPpiX9sAMyGr72y27afvfZxmlANjyRut9StOq9xk" + ); + + private readonly ECJwk _privateEcc384Key = ECJwk.FromBase64Url ( crv: EllipticalCurve.P384, - d: "Wf9qS_1idTtZ13HKUMkNDFPacwsfduJxayYtLlDGYzp8la9YajkWTPQwZT0X-vjq", x: "2ius4b5QcXto95wPhpQsX3IGAtnT9mNjMvds18_AgU3wNpOkppfuT6wu-y-fnsVU", - y: "3HPDrLpplnCJc3ksMBVD9rGFcAld3-c74CIk4ZNleOBnGeAkRZv4wJ4z_btwx_PL" + y: "3HPDrLpplnCJc3ksMBVD9rGFcAld3-c74CIk4ZNleOBnGeAkRZv4wJ4z_btwx_PL", + d: "Wf9qS_1idTtZ13HKUMkNDFPacwsfduJxayYtLlDGYzp8la9YajkWTPQwZT0X-vjq" ); - private readonly ECJwk _privateEcc384Key = ECJwk.FromBase64Url + private readonly ECJwk _publicEcc384Key = ECJwk.FromBase64Url ( crv: EllipticalCurve.P384, - d: "Wf9qS_1idTtZ13HKUMkNDFPacwsfduJxayYtLlDGYzp8la9YajkWTPQwZT0X-vjq", x: "2ius4b5QcXto95wPhpQsX3IGAtnT9mNjMvds18_AgU3wNpOkppfuT6wu-y-fnsVU", y: "3HPDrLpplnCJc3ksMBVD9rGFcAld3-c74CIk4ZNleOBnGeAkRZv4wJ4z_btwx_PL" ); @@ -61,9 +75,9 @@ public class JwsTokenTests private readonly ECJwk _privateEcc512Key = ECJwk.FromBase64Url ( crv: EllipticalCurve.P521, - d: "Adri8PbGJBWN5upp_67cKF8E0ADCF-w9WpI4vAnoE9iZsnRTZI9D20Ji9rzLyyEPp8KriI_HISTMh_RSmFFhTfBH", x: "AEeo_Y06znu6MVjyvJW2_SX_JKK2DxbxF3QjAqkZhMTvwgLc3Z073vFwwiCHKcOwK2b5H8H4a7PDN6DGJ6YJjpN0", - y: "AEESIwzgMrpPh9p_eq2EuIMUCCTPzaQK_DtXFwjOWsanjacwu1DZ3XSwbkiHvjQLrXDfdP7xZ-iAXQ1lGZqsud8y" + y: "AEESIwzgMrpPh9p_eq2EuIMUCCTPzaQK_DtXFwjOWsanjacwu1DZ3XSwbkiHvjQLrXDfdP7xZ-iAXQ1lGZqsud8y", + d: "Adri8PbGJBWN5upp_67cKF8E0ADCF-w9WpI4vAnoE9iZsnRTZI9D20Ji9rzLyyEPp8KriI_HISTMh_RSmFFhTfBH" ); private readonly ECJwk _publicEcc512Key = ECJwk.FromBase64Url @@ -79,6 +93,7 @@ public class JwsTokenTests public void Encode_Decode(string alg) { var (signingKey, validationKey) = SelectKeys(alg); + var writer = new JwtWriter(); var descriptor = new JwsDescriptor(signingKey, (SignatureAlgorithm)alg) { @@ -88,7 +103,7 @@ public void Encode_Decode(string alg) } }; - var token = writer.WriteToken(descriptor); + var token = writer.WriteTokenString(descriptor); var policy = new TokenValidationPolicyBuilder() .RequireSignatureByDefault(validationKey, (SignatureAlgorithm)alg) @@ -112,10 +127,13 @@ public static IEnumerable GetSupportedAlgorithm() yield return new object[] { (string)SignatureAlgorithm.PS256 }; yield return new object[] { (string)SignatureAlgorithm.PS384 }; yield return new object[] { (string)SignatureAlgorithm.PS512 }; -#if !NET461 +#if SUPPORT_ELLIPTIC_CURVE_SIGNATURE yield return new object[] { (string)SignatureAlgorithm.ES256 }; yield return new object[] { (string)SignatureAlgorithm.ES384 }; yield return new object[] { (string)SignatureAlgorithm.ES512 }; +#if !TARGET_MACOS + yield return new object[] { (string)SignatureAlgorithm.ES256K }; +#endif #endif } @@ -144,9 +162,11 @@ public static IEnumerable GetSupportedAlgorithm() case "PS512": return (_privateRsa2048Key, _publicRsa2048Key); -#if !NET461 +#if SUPPORT_ELLIPTIC_CURVE_SIGNATURE case "ES256": return (_privateEcc256Key, _publicEcc256Key); + case "ES256K": + return (_privateEcc256KKey, _publicEcc256KKey); case "ES384": return (_privateEcc384Key, _publicEcc384Key); case "ES512": diff --git a/test/JsonWebToken.Tests/PasswordBasedJwkTests.cs b/test/JsonWebToken.Tests/PasswordBasedJwkTests.cs index 1353aba8..d0f85ac4 100644 --- a/test/JsonWebToken.Tests/PasswordBasedJwkTests.cs +++ b/test/JsonWebToken.Tests/PasswordBasedJwkTests.cs @@ -64,7 +64,7 @@ public override void WriteTo() key.KeyOps.Add(JwkKeyOpsValues.Sign); key.Use = JwkUseValues.Sig; key.X5t = Base64Url.Decode("dGhpcyBpcyBhIFNIQTEgdGVzdCE"); - key.X5tS256 = Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA"); + key.X5tS256 = Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA"); key.X5u = "https://example.com"; key.X5c.Add(Convert.FromBase64String("MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==")); @@ -76,7 +76,7 @@ public override void WriteTo() Assert.Contains("\"key_ops\":[\"sign\"]", json); Assert.Contains("\"use\":\"sig\"", json); Assert.Contains("\"x5t\":\"dGhpcyBpcyBhIFNIQTEgdGVzdCE\"", json); - Assert.Contains("\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA\"", json); + Assert.Contains("\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA\"", json); #if NETSTANDARD2_0 Assert.Contains("\"x5u\":\"" + JsonEncodedText.Encode("https://example.com") + "\"", json); Assert.Contains("\"x5c\":[\"" + JsonEncodedText.Encode("MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==") + "\"]", json); diff --git a/test/JsonWebToken.Tests/RsaJwkTests.cs b/test/JsonWebToken.Tests/RsaJwkTests.cs index 31cd424f..1adf3e99 100644 --- a/test/JsonWebToken.Tests/RsaJwkTests.cs +++ b/test/JsonWebToken.Tests/RsaJwkTests.cs @@ -397,7 +397,7 @@ public override void WriteTo() key.KeyOps.Add(JwkKeyOpsValues.Sign); key.Use = JwkUseValues.Sig; key.X5t = Base64Url.Decode("dGhpcyBpcyBhIFNIQTEgdGVzdCE"); - key.X5tS256 = Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA"); + key.X5tS256 = Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA"); key.X5u = "https://example.com"; key.X5c.Add(Convert.FromBase64String("MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==")); @@ -409,7 +409,7 @@ public override void WriteTo() Assert.Contains("\"key_ops\":[\"sign\"]", json); Assert.Contains("\"use\":\"sig\"", json); Assert.Contains("\"x5t\":\"dGhpcyBpcyBhIFNIQTEgdGVzdCE\"", json); - Assert.Contains("\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA\"", json); + Assert.Contains("\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA\"", json); #if NETSTANDARD2_0 Assert.Contains("\"x5u\":\"" + JsonEncodedText.Encode("https://example.com") + "\"", json); Assert.Contains("\"x5c\":[\"" + JsonEncodedText.Encode("MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==") + "\"]", json); diff --git a/test/JsonWebToken.Tests/SymmetricJwkTests.cs b/test/JsonWebToken.Tests/SymmetricJwkTests.cs index bfb9ca89..f75dbc4f 100644 --- a/test/JsonWebToken.Tests/SymmetricJwkTests.cs +++ b/test/JsonWebToken.Tests/SymmetricJwkTests.cs @@ -146,7 +146,7 @@ public override void WriteTo() key.KeyOps.Add(JwkKeyOpsValues.Sign); key.Use = JwkUseValues.Sig; key.X5t = Base64Url.Decode("dGhpcyBpcyBhIFNIQTEgdGVzdCE"); - key.X5tS256 = Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA"); + key.X5tS256 = Base64Url.Decode("dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA"); key.X5u = "https://example.com"; key.X5c.Add(Convert.FromBase64String("MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==")); @@ -158,7 +158,7 @@ public override void WriteTo() Assert.Contains("\"key_ops\":[\"sign\"]", json); Assert.Contains("\"use\":\"sig\"", json); Assert.Contains("\"x5t\":\"dGhpcyBpcyBhIFNIQTEgdGVzdCE\"", json); - Assert.Contains("\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICAgIA\"", json); + Assert.Contains("\"x5t#S256\":\"dGhpcyBpcyBhIFNIQTI1NiB0ZXN0ISAgICAgICAgICA\"", json); #if NETSTANDARD2_0 Assert.Contains("\"x5u\":\"" + JsonEncodedText.Encode("https://example.com") + "\"", json); Assert.Contains("\"x5c\":[\"" + JsonEncodedText.Encode("MIIDQjCCAiqgAwIBAgIGATz/FuLiMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDAeFw0xMzAyMjEyMzI5MTVaFw0xODA4MTQyMjI5MTVaMGIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTzEPMA0GA1UEBxMGRGVudmVyMRwwGgYDVQQKExNQaW5nIElkZW50aXR5IENvcnAuMRcwFQYDVQQDEw5CcmlhbiBDYW1wYmVsbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL64zn8/QnHYMeZ0LncoXaEde1fiLm1jHjmQsF/449IYALM9if6amFtPDy2yvz3YlRij66s5gyLCyO7ANuVRJx1NbgizcAblIgjtdf/u3WG7K+IiZhtELto/A7Fck9Ws6SQvzRvOE8uSirYbgmj6He4iO8NCyvaK0jIQRMMGQwsU1quGmFgHIXPLfnpnfajr1rVTAwtgV5LEZ4Iel+W1GC8ugMhyr4/p1MtcIM42EA8BzE6ZQqC7VPqPvEjZ2dbZkaBhPbiZAS3YeYBRDWm1p1OZtWamT3cEvqqPpnjL1XyW+oyVVkaZdklLQp2Btgt9qr21m42f4wTw+Xrp6rCKNb0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAh8zGlfSlcI0o3rYDPBB07aXNswb4ECNIKG0CETTUxmXl9KUL+9gGlqCz5iWLOgWsnrcKcY0vXPG9J1r9AqBNTqNgHq2G03X09266X5CpOe1zFo+Owb1zxtp3PehFdfQJ610CDLEaS9V9Rqp17hCyybEpOGVwe8fnk+fbEL2Bo3UPGrpsHzUoaGpDftmWssZkhpBJKVMJyf/RuP2SmmaIzmnw9JiSlYhzo4tpzd5rFXhjRbg4zW9C+2qok+2+qDM1iJ684gPHMIY8aLWrdgQTxkumGmTqgawR+N5MDtdPTEQ0XfIBc2cJEUyMTY5MPvACWpkA6SdS4xSvdXK3IVfOWA==") + "\"]", json);