aboutsummaryrefslogtreecommitdiff
path: root/lib/Hashing.Portable
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-03-16 13:19:03 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-03-16 13:19:03 -0400
commit36f1c3db6df08c5abb098bfe049b30fc443fbb5c (patch)
tree0856d01a575405f2e51616662eb4fd4f533273f0 /lib/Hashing.Portable
parent6d2897cf5e55ae0df5c22d99a0b3f4097f8afecb (diff)
Zero alloc hmac jwt, plugin controller thread safety
Diffstat (limited to 'lib/Hashing.Portable')
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs31
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs30
-rw-r--r--lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs53
3 files changed, 101 insertions, 13 deletions
diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs
index e8bd13f..77874ba 100644
--- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs
+++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebKey.cs
@@ -410,15 +410,30 @@ namespace VNLib.Hashing.IdentityUtility
{
return null;
}
-
- //bin buffer for temp decoding
- using UnsafeMemoryHandle<byte> binBuffer = MemoryUtil.UnsafeAlloc<byte>(base64.Length + 16, false);
- //base64url decode
- ERRNO count = VnEncoding.Base64UrlDecode(base64, binBuffer.Span);
-
- //Return buffer or null if failed
- return count ? binBuffer.AsSpan(0, count).ToArray() : null;
+ //Use stack buffer
+ if(base64.Length <= 64)
+ {
+ //Use stack buffer
+ Span<byte> buffer = stackalloc byte[84];
+
+ //base64url decode
+ ERRNO count = VnEncoding.Base64UrlDecode(base64, buffer);
+
+ //Return buffer or null if failed
+ return count ? buffer[0.. (int)count].ToArray() : null;
+ }
+ else
+ {
+ //bin buffer for temp decoding
+ using UnsafeMemoryHandle<byte> binBuffer = MemoryUtil.UnsafeAlloc<byte>(base64.Length + 16, false);
+
+ //base64url decode
+ ERRNO count = VnEncoding.Base64UrlDecode(base64, binBuffer.Span);
+
+ //Return buffer or null if failed
+ return count ? binBuffer.AsSpan(0, count).ToArray() : null;
+ }
}
}
}
diff --git a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs
index 55e31fc..2bc4f24 100644
--- a/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs
+++ b/lib/Hashing.Portable/src/IdentityUtility/JsonWebToken.cs
@@ -301,7 +301,7 @@ namespace VNLib.Hashing.IdentityUtility
Span<byte> signatureBuffer = stackalloc byte[bufferSize];
//Compute the hash of the current payload
- if(!signatureAlgorithm.TryComputeHash(DataBuffer, signatureBuffer, out int bytesWritten))
+ if(!signatureAlgorithm.TryComputeHash(HeaderAndPayload, signatureBuffer, out int bytesWritten))
{
throw new InternalBufferTooSmallException();
}
@@ -326,7 +326,7 @@ namespace VNLib.Hashing.IdentityUtility
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public virtual void Sign(RSA rsa, in HashAlgorithmName hashAlg, RSASignaturePadding padding, int hashSize)
+ public virtual void Sign(RSA rsa, HashAlgorithmName hashAlg, RSASignaturePadding padding, int hashSize)
{
Check();
@@ -359,7 +359,7 @@ namespace VNLib.Hashing.IdentityUtility
/// <exception cref="OutOfMemoryException"></exception>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
- public virtual void Sign(ECDsa alg, in HashAlgorithmName hashAlg, int hashSize)
+ public virtual void Sign(ECDsa alg, HashAlgorithmName hashAlg, int hashSize)
{
Check();
@@ -383,6 +383,30 @@ namespace VNLib.Hashing.IdentityUtility
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]);
+ }
+
#endregion
///<inheritdoc/>
diff --git a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs
index fca2e75..3331738 100644
--- a/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs
+++ b/lib/Hashing.Portable/src/IdentityUtility/JwtExtensions.cs
@@ -204,6 +204,55 @@ namespace VNLib.Hashing.IdentityUtility
//Verify the signatures and return results
return CryptographicOperations.FixedTimeEquals(jwt.SignatureData, base64);
}
+
+ /// <summary>
+ /// Verifies the current JWT body-segements against the parsed signature segment.
+ /// </summary>
+ /// <param name="jwt"></param>
+ /// <param name="alg">
+ /// The HMAC algorithm to use when calculating the hash of the JWT
+ /// </param>
+ /// <param name="key">The HMAC shared symetric key</param>
+ /// <returns>
+ /// True if the signature field of the current JWT matches the re-computed signature of the header and data-fields
+ /// signature
+ /// </returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="InternalBufferTooSmallException"></exception>
+ public static bool Verify(this JsonWebToken jwt, ReadOnlySpan<byte> key, HashAlg alg)
+ {
+ _ = jwt ?? throw new ArgumentNullException(nameof(jwt));
+
+ //Get base64 buffer size for in-place conversion
+ int bufferSize = Base64.GetMaxEncodedToUtf8Length((int)alg);
+
+ //Alloc buffer for signature output
+ Span<byte> signatureBuffer = stackalloc byte[bufferSize];
+
+ //Compute the hash of the current payload
+ ERRNO count = ManagedHash.ComputeHmac(key, jwt.HeaderAndPayload, signatureBuffer, alg);
+ if (!count)
+ {
+ throw new InternalBufferTooSmallException("Failed to compute the hash of the JWT data");
+ }
+
+ //Do an in-place base64 conversion of the signature to base64
+ if (Base64.EncodeToUtf8InPlace(signatureBuffer, count, out int base64BytesWritten) != OperationStatus.Done)
+ {
+ throw new InternalBufferTooSmallException("Failed to convert the signature buffer to its base64 because the buffer was too small");
+ }
+
+ //Trim padding
+ Span<byte> base64 = signatureBuffer[..base64BytesWritten].Trim(JsonWebToken.PADDING_BYTES);
+
+ //Urlencode
+ VnEncoding.Base64ToUrlSafeInPlace(base64);
+
+ //Verify the signatures and return results
+ return CryptographicOperations.FixedTimeEquals(jwt.SignatureData, base64);
+ }
+
/// <summary>
/// Verifies the signature of the data using the specified <see cref="RSA"/> and hash parameters
/// </summary>
@@ -217,7 +266,7 @@ 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, in HashAlgorithmName hashAlg, RSASignaturePadding padding)
+ public static bool Verify(this JsonWebToken jwt, RSA alg, HashAlgorithmName hashAlg, RSASignaturePadding padding)
{
_ = jwt ?? throw new ArgumentNullException(nameof(jwt));
_ = alg ?? throw new ArgumentNullException(nameof(alg));
@@ -243,7 +292,7 @@ namespace VNLib.Hashing.IdentityUtility
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ObjectDisposedException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
- public static bool Verify(this JsonWebToken jwt, ECDsa alg, in HashAlgorithmName hashAlg)
+ public static bool Verify(this JsonWebToken jwt, ECDsa alg, HashAlgorithmName hashAlg)
{
_ = alg ?? throw new ArgumentNullException(nameof(alg));
//Decode the signature