aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/HashingExtensions.cs23
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureProvider.cs51
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/IJwtSignatureVerifier.cs43
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs36
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs126
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JwtClaim.cs11
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs300
-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.csproj2
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>