diff options
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs | 23 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureProvider.cs | 51 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureVerifier.cs | 43 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs | 36 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs | 126 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/JwtClaim.cs | 11 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs | 300 | ||||
-rw-r--r-- | lib/Net.Rest.Client/src/RestClientPool.cs (renamed from lib/Net.Rest.Client/src/ClientPool.cs) | 16 | ||||
-rw-r--r-- | lib/Net.Rest.Client/src/VNLib.Net.Rest.Client.csproj | 2 |
9 files changed, 425 insertions, 183 deletions
diff --git a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs index 5ff37e8..71a3cd3 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Hashing.Portable @@ -185,7 +185,7 @@ namespace VNLib.Hashing.IdentityUtility /// <exception cref="ArgumentNullException"></exception> /// <exception cref="CryptographicException"></exception> /// <exception cref="ObjectDisposedException"></exception> - public static ERRNO TryEncrypt(this RSA alg, ReadOnlySpan<char> data, in Span<byte> output, RSAEncryptionPadding padding, Encoding? enc = null) + public static ERRNO TryEncrypt(this RSA alg, ReadOnlySpan<char> data, Span<byte> output, RSAEncryptionPadding padding, Encoding? enc = null) { _ = alg ?? throw new ArgumentNullException(nameof(alg)); @@ -204,5 +204,24 @@ namespace VNLib.Hashing.IdentityUtility //Try encrypt return !alg.TryEncrypt(buffer.Span, output, padding, out int bytesWritten) ? ERRNO.E_FAIL : (ERRNO)bytesWritten; } + + /// <summary> + /// Gets the <see cref="HashAlgorithmName"/> for the current <see cref="HashAlg"/> + /// value. + /// </summary> + /// <param name="alg"></param> + /// <returns>The <see cref="HashAlgorithmName"/> of the current <see cref="HashAlg"/></returns> + public static HashAlgorithmName GetAlgName(this HashAlg alg) + { + return alg switch + { + HashAlg.SHA512 => HashAlgorithmName.SHA512, + HashAlg.SHA384 => HashAlgorithmName.SHA384, + HashAlg.SHA256 => HashAlgorithmName.SHA256, + HashAlg.SHA1 => HashAlgorithmName.SHA1, + HashAlg.MD5 => HashAlgorithmName.MD5, + _ => new(alg.ToString()), + }; + } } } diff --git a/lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureProvider.cs b/lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureProvider.cs new file mode 100644 index 0000000..ed9ba23 --- /dev/null +++ b/lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureProvider.cs @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: IJwtSignatureProvider.cs +* +* IJwtSignatureProvider.cs is part of VNLib.Hashing.Portable which is part +* of the larger VNLib collection of libraries and utilities. +* +* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Hashing.Portable is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +using VNLib.Utils; + +namespace VNLib.Hashing.IdentityUtility +{ + /// <summary> + /// Represents an objec that can compute signatures from a messages digest + /// and write the results to a buffer. + /// </summary> + public interface IJwtSignatureProvider + { + /// <summary> + /// Gets the size (in bytes) of the buffer required to store the signature output + /// </summary> + int RequiredBufferSize { get; } + + /// <summary> + /// Computes the signature from the message digest, and stores the results in the + /// output buffer + /// </summary> + /// <param name="hash">The message digest to compute the signature of</param> + /// <param name="outputBuffer">The buffer to write sigature data to</param> + /// <returns>The number of bytes written to the output buffer, or 0/fale if the operation failed</returns> + ERRNO ComputeSignatureFromHash(ReadOnlySpan<byte> hash, Span<byte> outputBuffer); + } +} diff --git a/lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureVerifier.cs b/lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureVerifier.cs new file mode 100644 index 0000000..149d5ed --- /dev/null +++ b/lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureVerifier.cs @@ -0,0 +1,43 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: IJwtSignatureVerifier.cs +* +* IJwtSignatureVerifier.cs is part of VNLib.Hashing.Portable which is +* part of the larger VNLib collection of libraries and utilities. +* +* VNLib.Hashing.Portable is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published +* by the Free Software Foundation, either version 2 of the License, +* or (at your option) any later version. +* +* VNLib.Hashing.Portable is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with VNLib.Hashing.Portable. If not, see http://www.gnu.org/licenses/. +*/ + +using System; + +namespace VNLib.Hashing.IdentityUtility +{ + /// <summary> + /// Represents an object that can verify a message hash against its signature to + /// confirm the message is authentic + /// </summary> + public interface IJwtSignatureVerifier + { + /// <summary> + /// Verifes that the message digest/hash matches the provided signature + /// </summary> + /// <param name="messageHash">The message digest to verify</param> + /// <param name="signature">The signature to confrim matches</param> + /// <returns>True if the signature matches the computed signature, false otherwise</returns> + bool Verify(ReadOnlySpan<byte> messageHash, ReadOnlySpan<byte> signature); + } +} diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs index 77874ba..8813e97 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs @@ -119,48 +119,48 @@ namespace VNLib.Hashing.IdentityUtility case JWKAlgorithms.RS256: { using RSA? rsa = GetRSAPublicKey(jwk); - return rsa != null && token.Verify(rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + return rsa != null && token.Verify(rsa, HashAlg.SHA256, RSASignaturePadding.Pkcs1); } case JWKAlgorithms.RS384: { using RSA? rsa = GetRSAPublicKey(jwk); - return rsa != null && token.Verify(rsa, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1); + return rsa != null && token.Verify(rsa, HashAlg.SHA384, RSASignaturePadding.Pkcs1); } case JWKAlgorithms.RS512: { using RSA? rsa = GetRSAPublicKey(jwk); - return rsa != null && token.Verify(rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1); + return rsa != null && token.Verify(rsa, HashAlg.SHA512, RSASignaturePadding.Pkcs1); } case JWKAlgorithms.PS256: { using RSA? rsa = GetRSAPublicKey(jwk); - return rsa != null && token.Verify(rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + return rsa != null && token.Verify(rsa, HashAlg.SHA256, RSASignaturePadding.Pss); } case JWKAlgorithms.PS384: { using RSA? rsa = GetRSAPublicKey(jwk); - return rsa != null && token.Verify(rsa, HashAlgorithmName.SHA384, RSASignaturePadding.Pss); + return rsa != null && token.Verify(rsa, HashAlg.SHA384, RSASignaturePadding.Pss); } case JWKAlgorithms.PS512: { using RSA? rsa = GetRSAPublicKey(jwk); - return rsa != null && token.Verify(rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pss); + return rsa != null && token.Verify(rsa, HashAlg.SHA512, RSASignaturePadding.Pss); } //Eccurves case JWKAlgorithms.ES256: { using ECDsa? eCDsa = GetECDsaPublicKey(jwk); - return eCDsa != null && token.Verify(eCDsa, HashAlgorithmName.SHA256); + return eCDsa != null && token.Verify(eCDsa, HashAlg.SHA256); } case JWKAlgorithms.ES384: { using ECDsa? eCDsa = GetECDsaPublicKey(jwk); - return eCDsa != null && token.Verify(eCDsa, HashAlgorithmName.SHA384); + return eCDsa != null && token.Verify(eCDsa, HashAlg.SHA384); } case JWKAlgorithms.ES512: { using ECDsa? eCDsa = GetECDsaPublicKey(jwk); - return eCDsa != null && token.Verify(eCDsa, HashAlgorithmName.SHA512); + return eCDsa != null && token.Verify(eCDsa, HashAlg.SHA512); } default: throw new EncryptionTypeNotSupportedException(); @@ -199,42 +199,42 @@ namespace VNLib.Hashing.IdentityUtility { using RSA? rsa = GetRSAPrivateKey(jwk); _ = rsa ?? throw new InvalidOperationException("JWK Does not contain an RSA private key"); - token.Sign(rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1, 128); + token.Sign(rsa, HashAlg.SHA256, RSASignaturePadding.Pkcs1); return; } case JWKAlgorithms.RS384: { using RSA? rsa = GetRSAPrivateKey(jwk); _ = rsa ?? throw new InvalidOperationException("JWK Does not contain an RSA private key"); - token.Sign(rsa, HashAlgorithmName.SHA384, RSASignaturePadding.Pkcs1, 128); + token.Sign(rsa, HashAlg.SHA384, RSASignaturePadding.Pkcs1); return; } case JWKAlgorithms.RS512: { using RSA? rsa = GetRSAPrivateKey(jwk); _ = rsa ?? throw new InvalidOperationException("JWK Does not contain an RSA private key"); - token.Sign(rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1, 256); + token.Sign(rsa, HashAlg.SHA512, RSASignaturePadding.Pkcs1); return; } case JWKAlgorithms.PS256: { using RSA? rsa = GetRSAPrivateKey(jwk); _ = rsa ?? throw new InvalidOperationException("JWK Does not contain an RSA private key"); - token.Sign(rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pss, 128); + token.Sign(rsa, HashAlg.SHA256, RSASignaturePadding.Pss); return; } case JWKAlgorithms.PS384: { using RSA? rsa = GetRSAPrivateKey(jwk); _ = rsa ?? throw new InvalidOperationException("JWK Does not contain an RSA private key"); - token.Sign(rsa, HashAlgorithmName.SHA384, RSASignaturePadding.Pss, 128); + token.Sign(rsa, HashAlg.SHA384, RSASignaturePadding.Pss); return; } case JWKAlgorithms.PS512: { using RSA? rsa = GetRSAPrivateKey(jwk); _ = rsa ?? throw new InvalidOperationException("JWK Does not contain an RSA private key"); - token.Sign(rsa, HashAlgorithmName.SHA512, RSASignaturePadding.Pss, 256); + token.Sign(rsa, HashAlg.SHA512, RSASignaturePadding.Pss); return; } //Eccurves @@ -242,21 +242,21 @@ namespace VNLib.Hashing.IdentityUtility { using ECDsa? eCDsa = GetECDsaPrivateKey(jwk); _ = eCDsa ?? throw new InvalidOperationException("JWK Does not contain an ECDsa private key"); - token.Sign(eCDsa, HashAlgorithmName.SHA256, 128); + token.Sign(eCDsa, HashAlg.SHA256); return; } case JWKAlgorithms.ES384: { using ECDsa? eCDsa = GetECDsaPrivateKey(jwk); _ = eCDsa ?? throw new InvalidOperationException("JWK Does not contain an ECDsa private key"); - token.Sign(eCDsa, HashAlgorithmName.SHA384, 128); + token.Sign(eCDsa, HashAlg.SHA384); return; } case JWKAlgorithms.ES512: { using ECDsa? eCDsa = GetECDsaPrivateKey(jwk); _ = eCDsa ?? throw new InvalidOperationException("JWK Does not contain an ECDsa private key"); - token.Sign(eCDsa, HashAlgorithmName.SHA512, 256); + token.Sign(eCDsa, HashAlg.SHA512); return; } default: diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs index 2bc4f24..6ee597e 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Hashing.Portable @@ -26,7 +26,6 @@ using System; using System.Text; using System.Buffers; using System.Buffers.Text; -using System.Security.Cryptography; using VNLib.Utils; using VNLib.Utils.IO; @@ -278,133 +277,24 @@ namespace VNLib.Hashing.IdentityUtility /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public ReadOnlySpan<byte> SignatureData => DataBuffer[SignatureStart..SignatureEnd]; - - /// <summary> - /// Signs the current JWT (header + payload) data - /// and writes the signature the end of the current buffer, - /// using the specified <see cref="HashAlgorithm"/>. - /// </summary> - /// <param name="signatureAlgorithm">An alternate <see cref="HashAlgorithm"/> instance to sign the JWT with</param> - /// <exception cref="OutOfMemoryException"></exception> - /// <exception cref="ArgumentNullException"></exception> - /// <exception cref="ObjectDisposedException"></exception> - public virtual void Sign(HashAlgorithm signatureAlgorithm) - { - Check(); - - _ = signatureAlgorithm ?? throw new ArgumentNullException(nameof(signatureAlgorithm)); - - //Calculate the size of the buffer to use for the current algorithm - int bufferSize = signatureAlgorithm.HashSize / 8; - - //Alloc buffer for signature output - Span<byte> signatureBuffer = stackalloc byte[bufferSize]; - //Compute the hash of the current payload - if(!signatureAlgorithm.TryComputeHash(HeaderAndPayload, signatureBuffer, out int bytesWritten)) - { - throw new InternalBufferTooSmallException(); - } - - //Reset the stream position to the end of the payload - DataStream.SetLength(PayloadEnd); - - //Write leading period - DataStream.WriteByte(SAEF_PERIOD); - - //Write the signature data to the buffer - WriteValue(signatureBuffer[..bytesWritten]); - } - /// <summary> - /// Use an RSA algorithm to sign the JWT message + /// Resets the internal buffer to the end of the payload, overwriting any previous + /// signature, and writes the sepcified signature to the internal buffer. /// </summary> - /// <param name="rsa">The algorithm used to sign the token</param> - /// <param name="hashAlg">The hash algorithm to use</param> - /// <param name="padding">The signature padding to use</param> - /// <param name="hashSize">The size (in bytes) of the hash output</param> - /// <exception cref="OutOfMemoryException"></exception> - /// <exception cref="ArgumentNullException"></exception> - /// <exception cref="ObjectDisposedException"></exception> - public virtual void Sign(RSA rsa, HashAlgorithmName hashAlg, RSASignaturePadding padding, int hashSize) + /// <param name="signature">The message signature.</param> + public virtual void WriteSignature(ReadOnlySpan<byte> signature) { Check(); - - _ = rsa ?? throw new ArgumentNullException(nameof(rsa)); - - //Calculate the size of the buffer to use for the current algorithm - using UnsafeMemoryHandle<byte> sigBuffer = Heap.UnsafeAlloc<byte>(hashSize); - - if(!rsa.TrySignData(HeaderAndPayload, sigBuffer.Span, hashAlg, padding, out int hashBytesWritten)) - { - throw new InternalBufferTooSmallException("Signature buffer is not large enough to store the hash"); - } - - //Reset the stream position to the end of the payload - DataStream.SetLength(PayloadEnd); - - //Write leading period - DataStream.WriteByte(SAEF_PERIOD); - - //Write the signature data to the buffer - WriteValue(sigBuffer.Span[..hashBytesWritten]); - } - - /// <summary> - /// Use an RSA algorithm to sign the JWT message - /// </summary> - /// <param name="alg">The algorithm used to sign the token</param> - /// <param name="hashAlg">The hash algorithm to use</param> - /// <param name="hashSize">The size (in bytes) of the hash output</param> - /// <exception cref="OutOfMemoryException"></exception> - /// <exception cref="ArgumentNullException"></exception> - /// <exception cref="ObjectDisposedException"></exception> - public virtual void Sign(ECDsa alg, HashAlgorithmName hashAlg, int hashSize) - { - Check(); - - _ = alg ?? throw new ArgumentNullException(nameof(alg)); - - //Calculate the size of the buffer to use for the current algorithm - using UnsafeMemoryHandle<byte> sigBuffer = Heap.UnsafeAlloc<byte>(hashSize); - - if (!alg.TrySignData(HeaderAndPayload, sigBuffer.Span, hashAlg, out int hashBytesWritten)) - { - throw new InternalBufferTooSmallException("Signature buffer is not large enough to store the hash"); - } - + //Reset the stream position to the end of the payload DataStream.SetLength(PayloadEnd); - + //Write leading period DataStream.WriteByte(SAEF_PERIOD); //Write the signature data to the buffer - WriteValue(sigBuffer.Span[..hashBytesWritten]); - } - - /// <summary> - /// Signs the JWT data using HMAC without allocating a <see cref="HashAlgorithm"/> - /// instance. - /// </summary> - /// <param name="alg">The algorithm used to sign</param> - /// <param name="key">The key data</param> - /// <exception cref="InternalBufferTooSmallException"></exception> - public virtual void Sign(ReadOnlySpan<byte> key, HashAlg alg) - { - //Stack hash output buffer, will be the size of the alg - Span<byte> sigOut = stackalloc byte[(int)alg]; - - //Compute - ERRNO count = ManagedHash.ComputeHmac(key, HeaderAndPayload, sigOut, alg); - - if (!count) - { - throw new InternalBufferTooSmallException("Failed to compute the hmac signature because the internal buffer was mis-sized"); - } - - //write - WriteValue(sigOut[..(int)count]); + WriteValue(signature); } #endregion diff --git a/lib/Hashing.Portable/src/IdentityUtility/JwtClaim.cs b/lib/Hashing.Portable/src/IdentityUtility/JwtClaim.cs index 55610a5..be8c682 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JwtClaim.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JwtClaim.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Hashing.Portable @@ -31,7 +31,7 @@ namespace VNLib.Hashing.IdentityUtility /// <summary> /// A fluent api structure for adding and committing claims to a <see cref="JsonWebToken"/> /// </summary> - public readonly struct JwtPayload : IIndexable<string, object> + public readonly record struct JwtPayload : IIndexable<string, object> { private readonly Dictionary<string, object> Claims; private readonly JsonWebToken Jwt; @@ -43,7 +43,7 @@ namespace VNLib.Hashing.IdentityUtility } ///<inheritdoc/> - public object this[string key] + public readonly object this[string key] { get => Claims[key]; set => Claims[key] = value; @@ -55,15 +55,16 @@ namespace VNLib.Hashing.IdentityUtility /// <param name="claim">The clame name</param> /// <param name="value">The value of the claim</param> /// <returns>The chained response object</returns> - public JwtPayload AddClaim(string claim, object value) + public readonly JwtPayload AddClaim(string claim, object value) { Claims.Add(claim, value); return this; } + /// <summary> /// Writes all claims to the <see cref="JsonWebToken"/> payload segment /// </summary> - public void CommitClaims() + public readonly void CommitClaims() { Jwt.WritePayload(Claims); Claims.Clear(); diff --git a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs index 3331738..38c40bf 100644 --- a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs +++ b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Hashing.Portable @@ -34,6 +34,7 @@ using VNLib.Utils.Extensions; namespace VNLib.Hashing.IdentityUtility { + /// <summary> /// Provides extension methods for manipulating /// and verifying <see cref="JsonWebToken"/>s @@ -52,6 +53,7 @@ namespace VNLib.Hashing.IdentityUtility byte[] data = JsonSerializer.SerializeToUtf8Bytes(header, jso); jwt.WriteHeader(data); } + /// <summary> /// Writes the message payload as the specified object /// </summary> @@ -81,14 +83,19 @@ namespace VNLib.Hashing.IdentityUtility { return JsonDocument.Parse("{}"); } + //calc padding bytes to add int paddingToAdd = CalcPadding(payload.Length); + //Alloc buffer to copy jwt payload data to using UnsafeMemoryHandle<byte> buffer = jwt.Heap.UnsafeAlloc<byte>(payload.Length + paddingToAdd); + //Decode from urlsafe base64 int decoded = DecodeUnpadded(payload, buffer.Span); + //Get json reader to read the first token (payload object) and return a document around it Utf8JsonReader reader = new(buffer.Span[..decoded]); + return JsonDocument.ParseValue(ref reader); } @@ -110,12 +117,16 @@ namespace VNLib.Hashing.IdentityUtility } //calc padding bytes to add int paddingToAdd = CalcPadding(header.Length); + //Alloc buffer to copy jwt header data to using UnsafeMemoryHandle<byte> buffer = jwt.Heap.UnsafeAlloc<byte>(header.Length + paddingToAdd); + //Decode from urlsafe base64 int decoded = DecodeUnpadded(header, buffer.Span); + //Get json reader to read the first token (payload object) and return a document around it Utf8JsonReader reader = new(buffer.Span[..decoded]); + return JsonDocument.ParseValue(ref reader); } @@ -150,14 +161,163 @@ namespace VNLib.Hashing.IdentityUtility } //calc padding bytes to add int paddingToAdd = CalcPadding(payload.Length); + //Alloc buffer to copy jwt payload data to using UnsafeMemoryHandle<byte> buffer = jwt.Heap.UnsafeAlloc<byte>(payload.Length + paddingToAdd); + //Decode from urlsafe base64 int decoded = DecodeUnpadded(payload, buffer.Span); + //Deserialze as an object return JsonSerializer.Deserialize<T>(buffer.Span[..decoded], jso); } - + + /// <summary> + /// Initializes a new <see cref="JwtPayload"/> object for writing claims to the + /// current tokens payload segment + /// </summary> + /// <param name="jwt"></param> + /// <param name="initCapacity">The inital cliam capacity</param> + /// <returns>The fluent chainable stucture</returns> + public static JwtPayload InitPayloadClaim(this JsonWebToken jwt, int initCapacity = 0) => new(jwt, initCapacity); + + /// <summary> + /// Signs the current JWT (header + payload) data + /// and writes the signature the end of the current buffer, + /// using the specified <see cref="HashAlgorithm"/>. + /// </summary> + /// <param name="jwt"></param> + /// <param name="signatureAlgorithm">An alternate <see cref="HashAlgorithm"/> instance to sign the JWT with</param> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + public static void Sign(this JsonWebToken jwt, HashAlgorithm signatureAlgorithm) + { + _ = signatureAlgorithm ?? throw new ArgumentNullException(nameof(signatureAlgorithm)); + + //Calculate the size of the buffer to use for the current algorithm + int bufferSize = signatureAlgorithm.HashSize / 8; + + //Alloc buffer for signature output + Span<byte> signatureBuffer = stackalloc byte[bufferSize]; + + //Compute the hash of the current payload + if (!signatureAlgorithm.TryComputeHash(jwt.HeaderAndPayload, signatureBuffer, out int bytesWritten)) + { + throw new InternalBufferTooSmallException(); + } + + jwt.WriteSignature(signatureBuffer[..bytesWritten]); + } + + /// <summary> + /// Use an RSA algorithm to sign the JWT message + /// </summary> + /// <param name="jwt"></param> + /// <param name="rsa">The algorithm used to sign the token</param> + /// <param name="alg">The <see cref="HashAlg"/> use to compute the message digest</param> + /// <param name="padding">The signature padding to use</param> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + public static void Sign(this JsonWebToken jwt, RSA rsa, HashAlg alg, RSASignaturePadding padding) + { + _ = rsa ?? throw new ArgumentNullException(nameof(rsa)); + + //Init new rsa provider + RSASignatureProvider sig = new(rsa, alg, padding); + + //Compute signature + jwt.Sign(in sig, alg); + } + + /// <summary> + /// Use an RSA algorithm to sign the JWT message + /// </summary> + /// <param name="jwt"></param> + /// <param name="alg">The algorithm used to sign the token</param> + /// <param name="hashAlg">The hash algorithm to use</param> + /// <param name="sigFormat">The DSA signature format</param> + /// <exception cref="OutOfMemoryException"></exception> + /// <exception cref="ArgumentNullException"></exception> + /// <exception cref="ObjectDisposedException"></exception> + public static void Sign(this JsonWebToken jwt, ECDsa alg, HashAlg hashAlg, DSASignatureFormat sigFormat = DSASignatureFormat.IeeeP1363FixedFieldConcatenation) + { + _ = alg ?? throw new ArgumentNullException(nameof(alg)); + + //Init new ec provider + ECDSASignatureProvider sig = new(alg, sigFormat); + + jwt.Sign(in sig, hashAlg); + } + + /// <summary> + /// Signs the JWT data using HMAC without allocating a <see cref="HashAlgorithm"/> + /// instance. + /// </summary> + /// <param name="jwt"></param> + /// <param name="alg">The algorithm used to sign</param> + /// <param name="key">The key data</param> + /// <exception cref="InternalBufferTooSmallException"></exception> + public static void Sign(this JsonWebToken jwt, ReadOnlySpan<byte> key, HashAlg alg) + { + //Stack hash output buffer, will be the size of the alg + Span<byte> sigOut = stackalloc byte[(int)alg]; + + //Compute + ERRNO count = ManagedHash.ComputeHmac(key, jwt.HeaderAndPayload, sigOut, alg); + + if (!count) + { + throw new InternalBufferTooSmallException("Failed to compute the hmac signature because the internal buffer was mis-sized"); + } + + //write + jwt.WriteSignature(sigOut[..(int)count]); + } + + /// <summary> + /// Computes the signature of the current <see cref="JsonWebToken"/> + /// using the generic <see cref="IJwtSignatureProvider"/> implementation. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="jwt"></param> + /// <param name="provider">The <see cref="IJwtSignatureProvider"/> that will compute the signature of the message digest</param> + /// <param name="hashAlg">The <see cref="HashAlg"/> algorithm used to compute the message digest</param> + /// <exception cref="InternalBufferTooSmallException"></exception> + /// <exception cref="CryptographicException"></exception> + public static void Sign<T>(this JsonWebToken jwt, in T provider, HashAlg hashAlg) where T : IJwtSignatureProvider + { + //Alloc heap buffer to store hash data (helps with memory locality) + nint nearestPage = MemoryUtil.NearestPage(provider.RequiredBufferSize + (int)hashAlg); + + //Alloc buffer + using UnsafeMemoryHandle<byte> handle = jwt.Heap.UnsafeAlloc<byte>((int)nearestPage, true); + + //Split buffers + Span<byte> hashBuffer = handle.Span[..(int)hashAlg]; + Span<byte> output = handle.Span[(int)hashAlg..]; + + //Compute hash + ERRNO hashLen = ManagedHash.ComputeHash(jwt.HeaderAndPayload, hashBuffer, hashAlg); + + if (!hashLen) + { + throw new InternalBufferTooSmallException("Hash buffer was not properly computed"); + } + + //Compute signature + ERRNO sigLen = provider.ComputeSignatureFromHash(hashBuffer[..(int)hashLen], output); + + if (!sigLen) + { + throw new CryptographicException("Failed to compute the JWT hash signature"); + } + + //Write signature to the jwt + jwt.WriteSignature(output[..(int)sigLen]); + } + /// <summary> /// Verifies the current JWT body-segements against the parsed signature segment. /// </summary> @@ -206,6 +366,53 @@ namespace VNLib.Hashing.IdentityUtility } /// <summary> + /// Verifies the signature of the current <see cref="JsonWebToken"/> against the + /// generic <see cref="IJwtSignatureVerifier"/> verification method. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <param name="jwt"></param> + /// <param name="provider">The <see cref="IJwtSignatureVerifier"/> used to verify the message digest</param> + /// <param name="alg">The <see cref="HashAlg"/> used to compute the message digest</param> + /// <returns>True if the siganture matches the computed on, false otherwise</returns> + /// <exception cref="InternalBufferTooSmallException"></exception> + public static bool Verify<T>(this JsonWebToken jwt, in T provider, HashAlg alg) where T : IJwtSignatureVerifier + { + ReadOnlySpan<byte> signature = jwt.SignatureData; + + int sigBufSize = CalcPadding(signature.Length) + signature.Length; + + //Calc full buffer size + nint bufferSize = MemoryUtil.NearestPage(sigBufSize + (int)alg); + + //Alloc buffer to decode data, as a full page, all buffers will be used from the block for better cache + using UnsafeMemoryHandle<byte> buffer = jwt.Heap.UnsafeAlloc<byte>((int)bufferSize); + + //Split buffers for locality + Span<byte> sigBuffer = buffer.Span[..sigBufSize]; + Span<byte> hashBuffer = buffer.Span[sigBufSize..]; + + //Decode from urlsafe base64 + int decoded = DecodeUnpadded(signature, sigBuffer); + + //Shift sig buffer to end of signature bytes + sigBuffer = sigBuffer[..decoded]; + + //Compute digest of message + ERRNO hashLen = ManagedHash.ComputeHash(jwt.HeaderAndPayload, hashBuffer, alg); + + if (!hashLen) + { + throw new InternalBufferTooSmallException("Hash output buffer was not properly sized"); + } + + //Shift hash buffer + hashBuffer = hashBuffer[..(int)hashLen]; + + //Verify signature + return provider.Verify(hashBuffer, sigBuffer); + } + + /// <summary> /// Verifies the current JWT body-segements against the parsed signature segment. /// </summary> /// <param name="jwt"></param> @@ -258,7 +465,7 @@ namespace VNLib.Hashing.IdentityUtility /// </summary> /// <param name="jwt"></param> /// <param name="alg">The RSA algorithim to use while verifying the signature of the payload</param> - /// <param name="hashAlg">The <see cref="HashAlgorithmName"/> used to hash the signature</param> + /// <param name="hashAlg">The <see cref="HashAlg"/> used to compute the digest of the message data</param> /// <param name="padding">The RSA signature padding method</param> /// <returns>True if the singature has been verified, false otherwise</returns> /// <exception cref="FormatException"></exception> @@ -266,53 +473,84 @@ namespace VNLib.Hashing.IdentityUtility /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static bool Verify(this JsonWebToken jwt, RSA alg, HashAlgorithmName hashAlg, RSASignaturePadding padding) + public static bool Verify(this JsonWebToken jwt, RSA alg, HashAlg hashAlg, RSASignaturePadding padding) { _ = jwt ?? throw new ArgumentNullException(nameof(jwt)); _ = alg ?? throw new ArgumentNullException(nameof(alg)); - //Decode the signature - ReadOnlySpan<byte> signature = jwt.SignatureData; - int paddBytes = CalcPadding(signature.Length); - //Alloc buffer to decode data - using UnsafeMemoryHandle<byte> buffer = jwt.Heap.UnsafeAlloc<byte>(signature.Length + paddBytes); - //Decode from urlsafe base64 - int decoded = DecodeUnpadded(signature, buffer.Span); - //Verify signature - return alg.VerifyData(jwt.HeaderAndPayload, buffer.Span[..decoded], hashAlg, padding); + + //Inint verifier + RSASignatureVerifier verifier = new(alg, hashAlg, padding); + + return jwt.Verify(in verifier, hashAlg); } + /// <summary> /// Verifies the signature of the data using the specified <see cref="RSA"/> and hash parameters /// </summary> /// <param name="jwt"></param> /// <param name="alg">The RSA algorithim to use while verifying the signature of the payload</param> /// <param name="hashAlg">The <see cref="HashAlgorithmName"/> used to hash the signature</param> + /// <param name="signatureFormat">The signatured format used to verify the token, defaults to field concatination</param> /// <returns>True if the singature has been verified, false otherwise</returns> /// <exception cref="FormatException"></exception> /// <exception cref="OutOfMemoryException"></exception> /// <exception cref="ArgumentNullException"></exception> /// <exception cref="ObjectDisposedException"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> - public static bool Verify(this JsonWebToken jwt, ECDsa alg, HashAlgorithmName hashAlg) + public static bool Verify(this JsonWebToken jwt, ECDsa alg, HashAlg hashAlg, DSASignatureFormat signatureFormat = DSASignatureFormat.IeeeP1363FixedFieldConcatenation) { _ = alg ?? throw new ArgumentNullException(nameof(alg)); - //Decode the signature - ReadOnlySpan<byte> signature = jwt.SignatureData; - int paddBytes = CalcPadding(signature.Length); - //Alloc buffer to decode data - using UnsafeMemoryHandle<byte> buffer = jwt.Heap.UnsafeAlloc<byte>(signature.Length + paddBytes); - //Decode from urlsafe base64 - int decoded = DecodeUnpadded(signature, buffer.Span); - //Verify signature - return alg.VerifyData(jwt.HeaderAndPayload, buffer.Span[..decoded], hashAlg); + + //Inint verifier + ECDSASignatureVerifier verifier = new(alg, signatureFormat); + + return jwt.Verify(in verifier, hashAlg); } - /// <summary> - /// Initializes a new <see cref="JwtPayload"/> object for writing claims to the - /// current tokens payload segment - /// </summary> - /// <param name="jwt"></param> - /// <param name="initCapacity">The inital cliam capacity</param> - /// <returns>The fluent chainable stucture</returns> - public static JwtPayload InitPayloadClaim(this JsonWebToken jwt, int initCapacity = 0) => new (jwt, initCapacity); + /* + * Simple ecdsa and rsa providers + */ + private record struct ECDSASignatureProvider(ECDsa SigAlg, DSASignatureFormat Format) : IJwtSignatureProvider + { + ///<inheritdoc/> + public readonly int RequiredBufferSize { get; } = 512; + + ///<inheritdoc/> + public readonly ERRNO ComputeSignatureFromHash(ReadOnlySpan<byte> hash, Span<byte> outputBuffer) + { + return SigAlg.TrySignHash(hash, outputBuffer, Format, out int written) ? written : ERRNO.E_FAIL; + } + } + + internal record struct RSASignatureProvider(RSA SigAlg, HashAlg Slg, RSASignaturePadding Padding) : IJwtSignatureProvider + { + ///<inheritdoc/> + public readonly int RequiredBufferSize { get; } = 1024; + + ///<inheritdoc/> + public readonly ERRNO ComputeSignatureFromHash(ReadOnlySpan<byte> hash, Span<byte> outputBuffer) + { + return !SigAlg.TrySignHash(hash, outputBuffer, Slg.GetAlgName(), Padding, out int written) ? written : ERRNO.E_FAIL; + } + } + + /* + * ECDSA and rsa verifiers + */ + internal record struct ECDSASignatureVerifier(ECDsa Alg, DSASignatureFormat Format) : IJwtSignatureVerifier + { + public readonly bool Verify(ReadOnlySpan<byte> messageHash, ReadOnlySpan<byte> signature) + { + return Alg.VerifyHash(messageHash, signature); + } + } + + internal record struct RSASignatureVerifier(RSA Alg, HashAlg hashAlg, RSASignaturePadding Padding) : IJwtSignatureVerifier + { + public readonly bool Verify(ReadOnlySpan<byte> messageHash, ReadOnlySpan<byte> signature) + { + return Alg.VerifyHash(messageHash, signature, hashAlg.GetAlgName(), Padding); + } + } } } diff --git a/lib/Net.Rest.Client/src/ClientPool.cs b/lib/Net.Rest.Client/src/RestClientPool.cs index 7d3bbd0..2d61203 100644 --- a/lib/Net.Rest.Client/src/ClientPool.cs +++ b/lib/Net.Rest.Client/src/RestClientPool.cs @@ -1,11 +1,11 @@ /* -* Copyright (c) 2022 Vaughn Nugent +* Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Net.Rest.Client -* File: ClientPool.cs +* File: RestClientPool.cs * -* ClientPool.cs is part of VNLib.Net.Rest.Client which is part of the larger +* RestClientPool.cs is part of VNLib.Net.Rest.Client which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Net.Rest.Client is free software: you can redistribute it and/or modify @@ -49,12 +49,12 @@ namespace VNLib.Net.Rest.Client public RestClientPool(int maxClients, RestClientOptions options, Action<RestClient>? initCb = null, IAuthenticator? authenticator = null) : base(() => { - RestClient client = new(options); //Add optional authenticator - if (authenticator != null) - { - client.UseAuthenticator(authenticator); - } + options.Authenticator = authenticator; + + //load client + RestClient client = new(options); + //Invoke init callback initCb?.Invoke(client); return client; diff --git a/lib/Net.Rest.Client/src/VNLib.Net.Rest.Client.csproj b/lib/Net.Rest.Client/src/VNLib.Net.Rest.Client.csproj index 1008df3..d25a307 100644 --- a/lib/Net.Rest.Client/src/VNLib.Net.Rest.Client.csproj +++ b/lib/Net.Rest.Client/src/VNLib.Net.Rest.Client.csproj @@ -41,7 +41,7 @@ <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> - <PackageReference Include="RestSharp" Version="108.0.3" /> + <PackageReference Include="RestSharp" Version="109.0.1" /> </ItemGroup> <ItemGroup> |