diff options
author | vnugent <public@vaughnnugent.com> | 2023-03-16 13:19:03 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2023-03-16 13:19:03 -0400 |
commit | 36f1c3db6df08c5abb098bfe049b30fc443fbb5c (patch) | |
tree | 0856d01a575405f2e51616662eb4fd4f533273f0 /lib/Hashing.Portable | |
parent | 6d2897cf5e55ae0df5c22d99a0b3f4097f8afecb (diff) |
Zero alloc hmac jwt, plugin controller thread safety
Diffstat (limited to 'lib/Hashing.Portable')
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 |