From 43542a261ec0789c7e48551ea5f9eaefa8c4b772 Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 20 Dec 2023 18:33:32 -0500 Subject: monocypher vendor and wrapper started, and partial public api updates --- lib/Hashing.Portable/src/Argon2/Argon2Context.cs | 44 +-- lib/Hashing.Portable/src/Argon2/VnArgon2.cs | 58 +-- lib/Hashing.Portable/src/HashAlg.cs | 72 ++++ lib/Hashing.Portable/src/HashEncodingMode.cs | 52 +++ lib/Hashing.Portable/src/ManagedHash.cs | 369 +++++++++--------- lib/Hashing.Portable/src/ManagedHashAlgImpl.cs | 227 +++++++++++ .../src/Native/MonoCypher/IHashStream.cs | 54 +++ .../src/Native/MonoCypher/IHmacStream.cs | 46 +++ .../src/Native/MonoCypher/MCBlake2Module.cs | 420 +++++++++++++++++++++ .../Native/MonoCypher/MCHashingStreamExtensions.cs | 292 ++++++++++++++ .../src/Native/MonoCypher/MCPasswordModule.cs | 159 ++++++++ .../src/Native/MonoCypher/MonoCypherLibrary.cs | 169 +++++++++ 12 files changed, 1741 insertions(+), 221 deletions(-) create mode 100644 lib/Hashing.Portable/src/HashAlg.cs create mode 100644 lib/Hashing.Portable/src/HashEncodingMode.cs create mode 100644 lib/Hashing.Portable/src/ManagedHashAlgImpl.cs create mode 100644 lib/Hashing.Portable/src/Native/MonoCypher/IHashStream.cs create mode 100644 lib/Hashing.Portable/src/Native/MonoCypher/IHmacStream.cs create mode 100644 lib/Hashing.Portable/src/Native/MonoCypher/MCBlake2Module.cs create mode 100644 lib/Hashing.Portable/src/Native/MonoCypher/MCHashingStreamExtensions.cs create mode 100644 lib/Hashing.Portable/src/Native/MonoCypher/MCPasswordModule.cs create mode 100644 lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs (limited to 'lib/Hashing.Portable/src') diff --git a/lib/Hashing.Portable/src/Argon2/Argon2Context.cs b/lib/Hashing.Portable/src/Argon2/Argon2Context.cs index 4631654..37abfbd 100644 --- a/lib/Hashing.Portable/src/Argon2/Argon2Context.cs +++ b/lib/Hashing.Portable/src/Argon2/Argon2Context.cs @@ -27,38 +27,34 @@ using System.Runtime.InteropServices; namespace VNLib.Hashing { - - public static unsafe partial class VnArgon2 + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal unsafe ref struct Argon2Context { - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] - private ref struct Argon2Context - { - public void* outptr; /* output array */ - public UInt32 outlen; /* digest length */ + public void* outptr; /* output array */ + public UInt32 outlen; /* digest length */ - public void* pwd; /* password array */ - public UInt32 pwdlen; /* password length */ + public void* pwd; /* password array */ + public UInt32 pwdlen; /* password length */ - public void* salt; /* salt array */ - public UInt32 saltlen; /* salt length */ + public void* salt; /* salt array */ + public UInt32 saltlen; /* salt length */ - public void* secret; /* key array */ - public UInt32 secretlen; /* key length */ + public void* secret; /* key array */ + public UInt32 secretlen; /* key length */ - public void* ad; /* associated data array */ - public UInt32 adlen; /* associated data length */ + public void* ad; /* associated data array */ + public UInt32 adlen; /* associated data length */ - public UInt32 t_cost; /* number of passes */ - public UInt32 m_cost; /* amount of memory requested (KB) */ - public UInt32 lanes; /* number of lanes */ - public UInt32 threads; /* maximum number of threads */ + public UInt32 t_cost; /* number of passes */ + public UInt32 m_cost; /* amount of memory requested (KB) */ + public UInt32 lanes; /* number of lanes */ + public UInt32 threads; /* maximum number of threads */ - public Argon2Version version; /* version number */ + public Argon2Version version; /* version number */ - public void* allocate_cbk; /* pointer to memory allocator */ - public void* free_cbk; /* pointer to memory deallocator */ + public void* allocate_cbk; /* pointer to memory allocator */ + public void* free_cbk; /* pointer to memory deallocator */ - public UInt32 flags; /* array of bool options */ - } + public UInt32 flags; /* array of bool options */ } } \ No newline at end of file diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs index 9b27194..9d98050 100644 --- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs +++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs @@ -24,7 +24,6 @@ using System; using System.Text; -using System.Buffers; using System.Threading; using System.Diagnostics; using System.Buffers.Text; @@ -34,6 +33,7 @@ using System.Runtime.InteropServices; using VNLib.Utils.Memory; using VNLib.Utils.Native; using VNLib.Utils.Extensions; +using VNLib.Hashing.Native.MonoCypher; namespace VNLib.Hashing { @@ -42,40 +42,46 @@ namespace VNLib.Hashing /// Implements the Argon2 data hashing library in .NET for cross platform use. /// /// Buffers are allocted on a private instance. - public static unsafe partial class VnArgon2 + public static unsafe class VnArgon2 { public const uint ARGON2_DEFAULT_FLAGS = 0U; public const uint HASH_SIZE = 128; public const int MAX_SALT_SIZE = 100; public const string ID_MODE = "argon2id"; public const string ARGON2_DEFUALT_LIB_NAME = "argon2"; - public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "ARGON2_DLL_PATH"; + public const string ARGON2_LIB_ENVIRONMENT_VAR_NAME = "VNLIB_ARGON2_DLL_PATH"; private static readonly Encoding LocEncoding = Encoding.Unicode; private static readonly Lazy _heap = new (static () => MemoryUtil.InitializeNewHeapForProcess(true), LazyThreadSafetyMode.PublicationOnly); - private static readonly Lazy _nativeLibrary = new(LoadSharedLibInternal, LazyThreadSafetyMode.PublicationOnly); + private static readonly Lazy _nativeLibrary = new(LoadSharedLibInternal, LazyThreadSafetyMode.PublicationOnly); //Private heap initialized to 10k size, and all allocated buffers will be zeroed when allocated - private static IUnmangedHeap PwHeap => _heap.Value; + private static IUnmangedHeap PwHeap => _heap.Value; - /* - * The native library delegate method - */ - [SafeMethodName("argon2id_ctx")] - delegate int Argon2InvokeHash(IntPtr context); - - private static SafeArgon2Library LoadSharedLibInternal() + private static IArgon2Library LoadSharedLibInternal() { //Get the path to the argon2 library string? argon2EnvPath = Environment.GetEnvironmentVariable(ARGON2_LIB_ENVIRONMENT_VAR_NAME); - //Default to the default library name - argon2EnvPath ??= ARGON2_DEFUALT_LIB_NAME; - Trace.WriteLine("Attempting to load global native Argon2 library from: " + argon2EnvPath, "VnArgon2"); + //If no native library is set, try to load the monocypher library + if (string.IsNullOrWhiteSpace(argon2EnvPath) && MonoCypherLibrary.CanLoadDefaultLibrary()) + { + Trace.WriteLine("Using the native MonoCypher library for Argon2 password hashing", "VnArgon2"); - SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(argon2EnvPath, DllImportSearchPath.SafeDirectories); - return new SafeArgon2Library(lib); + //Load shared monocyphter argon2 library + return MonoCypherLibrary.Shared.Argon2CreateLibrary(_heap.Value); + } + else + { + //Default to the default library name + argon2EnvPath ??= ARGON2_DEFUALT_LIB_NAME; + + Trace.WriteLine("Attempting to load global native Argon2 library from: " + argon2EnvPath, "VnArgon2"); + + SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(argon2EnvPath, DllImportSearchPath.SafeDirectories); + return new SafeArgon2Library(lib); + } } @@ -130,7 +136,7 @@ namespace VNLib.Hashing int passBytes = LocEncoding.GetByteCount(password); //Alloc memory for salt - using IMemoryHandle buffer = PwHeap.Alloc(saltbytes + passBytes); + using MemoryHandle buffer = PwHeap.Alloc(saltbytes + passBytes); Span saltBuffer = buffer.AsSpan(0, saltbytes); Span passBuffer = buffer.AsSpan(saltbytes, passBytes); @@ -175,7 +181,7 @@ namespace VNLib.Hashing int passBytes = LocEncoding.GetByteCount(password); //Alloc memory for password - using IMemoryHandle pwdHandle = PwHeap.Alloc(passBytes); + using MemoryHandle pwdHandle = PwHeap.Alloc(passBytes); //Encode password, create a new span to make sure its proper size _ = LocEncoding.GetBytes(password, pwdHandle.Span); @@ -223,9 +229,6 @@ namespace VNLib.Hashing //encode salt salts = Convert.ToBase64String(salt); - //Zero buffer - MemoryUtil.InitializeBlock(ref hashHandle.GetReference(), hashHandle.GetIntLength()); - //Encode salt in base64 return $"${ID_MODE}$v={(int)Argon2Version.Version13},m={costParams.MemoryCost},t={costParams.TimeCost},p={costParams.Parallelism},s={salts}${hash}"; } @@ -330,7 +333,7 @@ namespace VNLib.Hashing int rawPassLen = LocEncoding.GetByteCount(rawPass); //Alloc buffer for decoded data - using IMemoryHandle rawBufferHandle = PwHeap.Alloc(passBase64BufSize + saltBase64BufSize + rawPassLen); + using MemoryHandle rawBufferHandle = PwHeap.Alloc(passBase64BufSize + saltBase64BufSize + rawPassLen); //Split buffers Span saltBuf = rawBufferHandle.Span[..saltBase64BufSize]; @@ -393,10 +396,7 @@ namespace VNLib.Hashing ) { //Alloc data for hash output - using IMemoryHandle outputHandle = PwHeap.Alloc(hashBytes.Length); - - //Pin to get the base pointer - using MemoryHandle outputPtr = outputHandle.Pin(0); + using MemoryHandle outputHandle = PwHeap.Alloc(hashBytes.Length); //Get pointers fixed (byte* secretptr = secret, pwd = rawPass, slptr = salt) @@ -425,7 +425,7 @@ namespace VNLib.Hashing context->secret = secretptr; context->secretlen = (uint)secret.Length; //Output - context->outptr = outputPtr.Pointer; + context->outptr = outputHandle.Base; context->outlen = (uint)outputHandle.Length; //Hash Argon2_ErrorCodes argResult = (Argon2_ErrorCodes)lib.Argon2Hash((IntPtr)context); @@ -441,7 +441,7 @@ namespace VNLib.Hashing return result; } - private static void ThrowOnArgonErr(Argon2_ErrorCodes result) + internal static void ThrowOnArgonErr(Argon2_ErrorCodes result) { switch (result) { diff --git a/lib/Hashing.Portable/src/HashAlg.cs b/lib/Hashing.Portable/src/HashAlg.cs new file mode 100644 index 0000000..030e7e6 --- /dev/null +++ b/lib/Hashing.Portable/src/HashAlg.cs @@ -0,0 +1,72 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: HashAlg.cs +* +* HashAlg.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 VNLib.Hashing.Native.MonoCypher; + +namespace VNLib.Hashing +{ + /// + /// Defines a hashing algorithm to use when computing a hash. + /// + public enum HashAlg + { + /// + /// Unused type, will cause a computation method to raise an argument exception when used. + /// + None, + /// + /// Defines the SHA-512 hashing algorithm + /// + SHA512 = 64, + /// + /// Defines the SHA-384 hashing algorithm + /// + SHA384 = 48, + /// + /// Defines the SHA-256 hashing algorithm + /// + SHA256 = 32, + /// + /// Defines the SHA-1 hashing algorithm + /// WARNING: This hashing method is considered insecure and cannot be corrected. + /// + SHA1 = 20, + /// + /// Defines the MD5 hashing algorithm + /// WARNING: This hashing method is considered insecure and cannot be corrected. + /// + MD5 = 16, + + /* + * The blake2 value is negative because the hash size is variable and the enum value + * and cannot be used to determine the hash buffer size. + */ + /// + /// Defines the BLAKE2B hashing algorithm + /// NOTE: This hashing method may not be supported on all platforms, you should check for support before using it. + /// Inspect the value of + /// + BlAKE2B = -MCBlake2Module.MaxHashSize, + } +} diff --git a/lib/Hashing.Portable/src/HashEncodingMode.cs b/lib/Hashing.Portable/src/HashEncodingMode.cs new file mode 100644 index 0000000..cc1aa8c --- /dev/null +++ b/lib/Hashing.Portable/src/HashEncodingMode.cs @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: HashEncodingMode.cs +* +* HashEncodingMode.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 +{ + /// + /// The binary hash encoding type + /// + [Flags] + public enum HashEncodingMode + { + /// + /// Specifies the Base64 character encoding + /// + Base64 = 64, + /// + /// Specifies the hexadecimal character encoding + /// + Hexadecimal = 16, + /// + /// Specifies the Base32 character encoding + /// + Base32 = 32, + /// + /// Specifies the base64 URL safe character encoding + /// + Base64Url = 128 + } +} diff --git a/lib/Hashing.Portable/src/ManagedHash.cs b/lib/Hashing.Portable/src/ManagedHash.cs index 43c2a7c..5933804 100644 --- a/lib/Hashing.Portable/src/ManagedHash.cs +++ b/lib/Hashing.Portable/src/ManagedHash.cs @@ -28,61 +28,11 @@ using System.Security.Cryptography; using VNLib.Utils; using VNLib.Utils.Memory; +using VNLib.Hashing.Native.MonoCypher; +using VNLib.Utils.Extensions; namespace VNLib.Hashing { - /// - /// Defines a hashing algorithm to use when computing a hash. - /// - public enum HashAlg - { - /// - /// Unused type, will cause a computation method to raise an argument exception when used. - /// - None, - /// - /// Defines the SHA-512 hashing algorithm - /// - SHA512 = 64, - /// - /// Defines the SHA-384 hashing algorithm - /// - SHA384 = 48, - /// - /// Defines the SHA-256 hashing algorithm - /// - SHA256 = 32, - /// - /// Defines the SHA-1 hashing algorithm - /// WARNING: This hashing method is considered insecure and cannot be corrected. - /// - SHA1 = 20, - /// - /// Defines the MD5 hashing algorithm - /// WARNING: This hashing method is considered insecure and cannot be corrected. - /// - MD5 = 16 - } - - /// - /// The binary hash encoding type - /// - [Flags] - public enum HashEncodingMode - { - /// - /// Specifies the Base64 character encoding - /// - Base64 = 64, - /// - /// Specifies the hexadecimal character encoding - /// - Hexadecimal = 16, - /// - /// Specifies the Base32 character encoding - /// - Base32 = 32 - } /// /// Provides simple methods for common managed hashing functions @@ -119,6 +69,19 @@ namespace VNLib.Hashing }; } + private static readonly Sha1 _sha1Alg; + private static readonly Sha256 _sha256Alg; + private static readonly Sha384 _sha384Alg; + private static readonly Sha512 _sha512Alg; + private static readonly Md5 _md5Alg; + private static readonly Blake2b _blake2bAlg; + + /// + /// Gets a value that indicates whether the current runtime has the required libraries + /// available to support the Blake2b hashing algorithm + /// + public static bool SupportsBlake2b => MonoCypherLibrary.CanLoadDefaultLibrary(); + /// /// Uses the UTF8 character encoding to encode the string, then /// attempts to compute the hash and store the results into the output buffer @@ -130,16 +93,16 @@ namespace VNLib.Hashing /// public static ERRNO ComputeHash(ReadOnlySpan data, Span buffer, HashAlg type) { - int byteCount = CharEncoding.GetByteCount(data); - - //Alloc buffer - using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); - - //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf.Span); - - //hash the buffer - return ComputeHash(binbuf.Span[..byteCount], buffer, type); + return type switch + { + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, buffer), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, buffer), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, buffer), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, buffer), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, buffer), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, buffer), + _ => throw new ArgumentException("Invalid hash algorithm", nameof(type)) + }; } /// @@ -152,13 +115,16 @@ namespace VNLib.Hashing /// public static byte[] ComputeHash(ReadOnlySpan data, HashAlg type) { - int byteCount = CharEncoding.GetByteCount(data); - //Alloc buffer - using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); - //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf.Span); - //hash the buffer - return ComputeHash(binbuf.Span[..byteCount], type); + return type switch + { + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data), + _ => throw new ArgumentException("Invalid hash algorithm", nameof(type)) + }; } /// @@ -174,11 +140,12 @@ namespace VNLib.Hashing //hash the buffer return type switch { - HashAlg.SHA512 => SHA512.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL, - HashAlg.SHA384 => SHA384.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL, - HashAlg.SHA256 => SHA256.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL, - HashAlg.SHA1 => SHA1.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL, - HashAlg.MD5 => MD5.TryHashData(data, output, out int count) ? count : ERRNO.E_FAIL, + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, output), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, output), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, output), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, output), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, output), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, output), _ => throw new ArgumentException("Hash algorithm is not supported"), }; } @@ -195,12 +162,13 @@ namespace VNLib.Hashing //hash the buffer return type switch { - HashAlg.SHA512 => SHA512.HashData(data), - HashAlg.SHA384 => SHA384.HashData(data), - HashAlg.SHA256 => SHA256.HashData(data), - HashAlg.SHA1 => SHA1.HashData(data), - HashAlg.MD5 => MD5.HashData(data), - _ => throw new ArgumentException("Hash algorithm is not supported"), + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data), + _ => throw new ArgumentException("Invalid hash algorithm", nameof(type)) }; } @@ -216,21 +184,15 @@ namespace VNLib.Hashing /// public static string ComputeHash(ReadOnlySpan data, HashAlg type, HashEncodingMode mode) { - //Alloc hash buffer - Span hashBuffer = stackalloc byte[(int)type]; - //hash the buffer - ERRNO count = ComputeHash(data, hashBuffer, type); - if (!count) - { - throw new CryptographicException("Failed to compute the hash of the data"); - } - //Convert to hex string - return mode switch + return type switch { - HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)), - HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)), - HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)), - _ => throw new ArgumentException("Encoding mode is not supported"), + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, mode), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, mode), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, mode), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, mode), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, mode), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, mode), + _ => throw new ArgumentException("Invalid hash algorithm", nameof(type)) }; } @@ -247,30 +209,18 @@ namespace VNLib.Hashing /// public static string ComputeHash(ReadOnlySpan data, HashAlg type, HashEncodingMode mode) { - //Alloc hash buffer - Span hashBuffer = stackalloc byte[(int)type]; - //hash the buffer - ERRNO count = ComputeHash(data, hashBuffer, type); - if (!count) - { - throw new CryptographicException("Failed to compute the hash of the data"); - } - //Convert to hex string - return mode switch + return type switch { - HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)), - HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)), - HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)), - _ => throw new ArgumentException("Encoding mode is not supported"), + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, mode), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, mode), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, mode), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, mode), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, mode), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, mode), + _ => throw new ArgumentException("Invalid hash algorithm", nameof(type)) }; } - - public static string ComputeHexHash(ReadOnlySpan data, HashAlg type) => ComputeHash(data, type, HashEncodingMode.Hexadecimal); - public static string ComputeBase64Hash(ReadOnlySpan data, HashAlg type) => ComputeHash(data, type, HashEncodingMode.Base64); - public static string ComputeHexHash(ReadOnlySpan data, HashAlg type) => ComputeHash(data, type, HashEncodingMode.Hexadecimal); - public static string ComputeBase64Hash(ReadOnlySpan data, HashAlg type) => ComputeHash(data, type, HashEncodingMode.Base64); - /// /// Computes the HMAC of the specified character buffer using the specified key and /// writes the resuts to the output buffer. @@ -283,16 +233,16 @@ namespace VNLib.Hashing /// public static ERRNO ComputeHmac(ReadOnlySpan key, ReadOnlySpan data, Span output, HashAlg type) { - int byteCount = CharEncoding.GetByteCount(data); - - //Alloc buffer - using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); - - //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf.Span); - - //hash the buffer - return ComputeHmac(key, binbuf.Span[..byteCount], output, type); + return type switch + { + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, output, key), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, output, key), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, output, key), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, output, key), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, output, key), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, output, key), + _ => ERRNO.E_FAIL + }; } /// @@ -306,17 +256,18 @@ namespace VNLib.Hashing /// public static byte[] ComputeHmac(ReadOnlySpan key, ReadOnlySpan data, HashAlg type) { - int byteCount = CharEncoding.GetByteCount(data); - - //Alloc buffer - using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); - - //Encode data - byteCount = CharEncoding.GetBytes(data, binbuf.Span); - - //hash the buffer - return ComputeHmac(key, binbuf.Span[..byteCount], type); + return type switch + { + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, key), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, key), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, key), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, key), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, key), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, key), + _ => throw new ArgumentException("Hash algorithm is not supported"), + }; } + /// /// Computes the HMAC of the specified data buffer using the specified key and /// writes the resuts to the output buffer. @@ -332,11 +283,12 @@ namespace VNLib.Hashing //hash the buffer return type switch { - HashAlg.SHA512 => HMACSHA512.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL, - HashAlg.SHA384 => HMACSHA384.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL, - HashAlg.SHA256 => HMACSHA256.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL, - HashAlg.SHA1 => HMACSHA1.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL, - HashAlg.MD5 => HMACMD5.TryHashData(key, data, output, out int count) ? count : ERRNO.E_FAIL, + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, output, key), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, output, key), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, output, key), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, output, key), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, output, key), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, output, key), _ => throw new ArgumentException("Hash algorithm is not supported"), }; } @@ -355,11 +307,12 @@ namespace VNLib.Hashing //hash the buffer return type switch { - HashAlg.SHA512 => HMACSHA512.HashData(key, data), - HashAlg.SHA384 => HMACSHA384.HashData(key, data), - HashAlg.SHA256 => HMACSHA256.HashData(key, data), - HashAlg.SHA1 => HMACSHA1.HashData(key, data), - HashAlg.MD5 => HMACMD5.HashData(key, data), + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, key), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, key), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, key), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, key), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, key), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, key), _ => throw new ArgumentException("Hash algorithm is not supported"), }; } @@ -376,24 +329,15 @@ namespace VNLib.Hashing /// public static string ComputeHmac(ReadOnlySpan key, ReadOnlySpan data, HashAlg type, HashEncodingMode mode) { - //Alloc hash buffer - Span hashBuffer = stackalloc byte[(int)type]; - - //hash the buffer - ERRNO count = ComputeHmac(key, data, hashBuffer, type); - - if (!count) - { - throw new InternalBufferTooSmallException("Failed to compute the hash of the data"); - } - - //Convert to hex string - return mode switch + return type switch { - HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)), - HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)), - HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)), - _ => throw new ArgumentException("Encoding mode is not supported"), + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, mode, key), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, mode, key), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, mode, key), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, mode, key), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, mode, key), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, mode, key), + _ => throw new ArgumentException("Invalid hash algorithm", nameof(type)) }; } @@ -409,25 +353,114 @@ namespace VNLib.Hashing /// public static string ComputeHmac(ReadOnlySpan key, ReadOnlySpan data, HashAlg type, HashEncodingMode mode) { - //Alloc hash buffer - Span hashBuffer = stackalloc byte[(int)type]; + return type switch + { + HashAlg.BlAKE2B => ComputeHashInternal(in _blake2bAlg, data, mode, key), + HashAlg.SHA512 => ComputeHashInternal(in _sha512Alg, data, mode, key), + HashAlg.SHA384 => ComputeHashInternal(in _sha384Alg, data, mode, key), + HashAlg.SHA256 => ComputeHashInternal(in _sha256Alg, data, mode, key), + HashAlg.SHA1 => ComputeHashInternal(in _sha1Alg, data, mode, key), + HashAlg.MD5 => ComputeHashInternal(in _md5Alg, data, mode, key), + _ => throw new ArgumentException("Invalid hash algorithm", nameof(type)) + }; + } + + #region internal + + private static byte[] ComputeHashInternal(in T algorithm, ReadOnlySpan data, ReadOnlySpan key = default) where T : IHashAlgorithm + { + int byteCount = CharEncoding.GetByteCount(data); + //Alloc buffer + using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); + //Encode data + byteCount = CharEncoding.GetBytes(data, binbuf.Span); + //hash the buffer + return ComputeHashInternal(in algorithm, binbuf.AsSpan(0, byteCount), key); + } + + private static string ComputeHashInternal(in T algorithm, ReadOnlySpan data, HashEncodingMode mode, ReadOnlySpan key = default) where T : IHashAlgorithm + { + //Alloc stack buffer to store hash output + Span hashBuffer = stackalloc byte[algorithm.HashSize]; + //hash the buffer + ERRNO count = ComputeHashInternal(in algorithm, data, hashBuffer, key); + if (!count) + { + throw new CryptographicException("Failed to compute the hash of the data"); + } + + //Convert to encoded string + return mode switch + { + HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)), + HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)), + HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)), + HashEncodingMode.Base64Url => VnEncoding.ToBase64UrlSafeString(hashBuffer.Slice(0, count), true), + _ => throw new ArgumentException("Encoding mode is not supported"), + }; + } + + private static string ComputeHashInternal(in T algorithm, ReadOnlySpan data, HashEncodingMode mode, ReadOnlySpan key = default) where T : IHashAlgorithm + { + //Alloc stack buffer to store hash output + Span hashBuffer = stackalloc byte[algorithm.HashSize]; //hash the buffer - ERRNO count = ComputeHmac(key, data, hashBuffer, type); - + ERRNO count = ComputeHashInternal(in algorithm, data, hashBuffer, key); if (!count) { - throw new InternalBufferTooSmallException("Failed to compute the hash of the data"); + throw new CryptographicException("Failed to compute the hash of the data"); } - - //Convert to hex string + + //Convert to encoded string return mode switch { HashEncodingMode.Hexadecimal => Convert.ToHexString(hashBuffer.Slice(0, count)), HashEncodingMode.Base64 => Convert.ToBase64String(hashBuffer.Slice(0, count)), HashEncodingMode.Base32 => VnEncoding.ToBase32String(hashBuffer.Slice(0, count)), + HashEncodingMode.Base64Url => VnEncoding.ToBase64UrlSafeString(hashBuffer.Slice(0, count), true), _ => throw new ArgumentException("Encoding mode is not supported"), }; } + + private static ERRNO ComputeHashInternal(in T algorithm, ReadOnlySpan data, Span output, ReadOnlySpan key = default) where T : IHashAlgorithm + { + int byteCount = CharEncoding.GetByteCount(data); + //Alloc buffer + using UnsafeMemoryHandle binbuf = MemoryUtil.UnsafeAlloc(byteCount, true); + //Encode data + byteCount = CharEncoding.GetBytes(data, binbuf.Span); + //hash the buffer or hmac if key is not empty + return ComputeHashInternal(in algorithm, binbuf.Span[..byteCount], output, key); + } + + + private static ERRNO ComputeHashInternal(in T algorithm, ReadOnlySpan data, Span buffer, ReadOnlySpan key = default) where T : IHashAlgorithm + { + //hash the buffer or hmac if key is not empty + if (key.IsEmpty) + { + return algorithm.TryComputeHash(data, buffer, out int written) ? written : ERRNO.E_FAIL; + } + else + { + return algorithm.TryComputeHmac(key, data, buffer, out int written) ? written : ERRNO.E_FAIL; + } + } + + private static byte[] ComputeHashInternal(in T algorithm, ReadOnlySpan data, ReadOnlySpan key = default) where T : IHashAlgorithm + { + //hash the buffer or hmac if key is not empty + if (key.IsEmpty) + { + return algorithm.ComputeHash(data); + } + else + { + return algorithm.ComputeHmac(key, data); + } + } + + #endregion } } diff --git a/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs new file mode 100644 index 0000000..7e2262e --- /dev/null +++ b/lib/Hashing.Portable/src/ManagedHashAlgImpl.cs @@ -0,0 +1,227 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: ManagedHashAlgImpl.cs +* +* ManagedHashAlgImpl.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 System.Diagnostics; +using System.Security.Cryptography; + +using VNLib.Utils; +using VNLib.Hashing.Native.MonoCypher; + +namespace VNLib.Hashing +{ + public static partial class ManagedHash + { + private interface IHashAlgorithm + { + int HashSize { get; } + + byte[] ComputeHash(ReadOnlySpan data); + + bool TryComputeHash(ReadOnlySpan data, Span output, out int count); + + byte[] ComputeHmac(ReadOnlySpan key, ReadOnlySpan data); + + bool TryComputeHmac(ReadOnlySpan key, ReadOnlySpan data, Span output, out int count); + } + + private readonly struct Sha1 : IHashAlgorithm + { + /// + public readonly int HashSize => (int)HashAlg.SHA1; + /// + public readonly byte[] ComputeHash(ReadOnlySpan data) => SHA1.HashData(data); + /// + public readonly bool TryComputeHash(ReadOnlySpan data, Span output, out int count) => SHA1.TryHashData(data, output, out count); + + /// + public readonly byte[] ComputeHmac(ReadOnlySpan key, ReadOnlySpan data) => HMACSHA1.HashData(key, data); + /// + public readonly bool TryComputeHmac(ReadOnlySpan key, ReadOnlySpan data, Span output, out int count) => HMACSHA1.TryHashData(key, data, output, out count); + } + + private readonly struct Sha256 : IHashAlgorithm + { + /// + public readonly int HashSize => (int)HashAlg.SHA256; + + /// + public readonly byte[] ComputeHash(ReadOnlySpan data) => SHA256.HashData(data); + + /// + public readonly bool TryComputeHash(ReadOnlySpan data, Span output, out int count) => SHA256.TryHashData(data, output, out count); + + /// + public readonly byte[] ComputeHmac(ReadOnlySpan key, ReadOnlySpan data) => HMACSHA256.HashData(key, data); + + /// + public readonly bool TryComputeHmac(ReadOnlySpan key, ReadOnlySpan data, Span output, out int count) => HMACSHA256.TryHashData(key, data, output, out count); + } + + private readonly struct Sha384 : IHashAlgorithm + { + /// + public readonly int HashSize => (int)HashAlg.SHA384; + /// + public readonly byte[] ComputeHash(ReadOnlySpan data) => SHA384.HashData(data); + /// + public readonly bool TryComputeHash(ReadOnlySpan data, Span output, out int count) => SHA384.TryHashData(data, output, out count); + + /// + public readonly byte[] ComputeHmac(ReadOnlySpan key, ReadOnlySpan data) => HMACSHA384.HashData(key, data); + + /// + public readonly bool TryComputeHmac(ReadOnlySpan key, ReadOnlySpan data, Span output, out int count) => HMACSHA384.TryHashData(key, data, output, out count); + } + + private readonly struct Sha512 : IHashAlgorithm + { + /// + public readonly int HashSize => (int)HashAlg.SHA512; + /// + public readonly byte[] ComputeHash(ReadOnlySpan data) => SHA512.HashData(data); + /// + public readonly bool TryComputeHash(ReadOnlySpan data, Span output, out int count) => SHA512.TryHashData(data, output, out count); + + /// + public readonly byte[] ComputeHmac(ReadOnlySpan key, ReadOnlySpan data) => HMACSHA512.HashData(key, data); + /// + public readonly bool TryComputeHmac(ReadOnlySpan key, ReadOnlySpan data, Span output, out int count) => HMACSHA512.TryHashData(key, data, output, out count); + } + + private readonly struct Md5 : IHashAlgorithm + { + /// + public readonly int HashSize => (int)HashAlg.MD5; + /// + public readonly byte[] ComputeHash(ReadOnlySpan data) => MD5.HashData(data); + /// + public readonly bool TryComputeHash(ReadOnlySpan data, Span output, out int count) => MD5.TryHashData(data, output, out count); + + /// + public readonly byte[] ComputeHmac(ReadOnlySpan key, ReadOnlySpan data) => HMACMD5.HashData(key, data); + /// + public readonly bool TryComputeHmac(ReadOnlySpan key, ReadOnlySpan data, Span output, out int count) => HMACMD5.TryHashData(key, data, output, out count); + } + + private readonly struct Blake2b : IHashAlgorithm + { + const byte DefaultBlake2HashSize = 64; + + internal static int MaxHashSize => MCBlake2Module.MaxHashSize; + internal static int MaxKeySize => MCBlake2Module.MaxKeySize; + + /// + public readonly int HashSize => DefaultBlake2HashSize; + + /// + public readonly byte[] ComputeHash(ReadOnlySpan data) + { + //Stack buffer for output hash + byte[] output = new byte[DefaultBlake2HashSize]; + + if (!TryComputeHash(data, output, out int count)) + { + throw new ArgumentException("Failed to compute Blake2 hash of desired data"); + } + + //Count must be exact same (sanity check) + Debug.Assert(count == DefaultBlake2HashSize); + + //Return the hash as a new array + return output; + } + + /// + public readonly byte[] ComputeHmac(ReadOnlySpan key, ReadOnlySpan data) + { + //Alloc output buffer + byte[] output = new byte[DefaultBlake2HashSize]; + + if (!TryComputeHmac(key, data, output, out int count)) + { + throw new ArgumentException("Failed to compute Blake2 hash of desired data"); + } + + //Count must be exact same (sanity check) + Debug.Assert(count == DefaultBlake2HashSize); + + //Return the hash as a new array + return output; + + } + + /// + public readonly bool TryComputeHash(ReadOnlySpan data, Span output, out int count) + { + if (output.Length > MCBlake2Module.MaxHashSize) + { + count = 0; + return false; + } + + //Compute one-shot hash + ERRNO result = MonoCypherLibrary.Shared.Blake2ComputeHash(data, output); + + if(result < output.Length) + { + count = 0; + return false; + } + + count = output.Length; + return true; + } + + /// + public readonly bool TryComputeHmac(ReadOnlySpan key, ReadOnlySpan data, Span output, out int count) + { + count = 0; + + if (output.Length > MCBlake2Module.MaxHashSize) + { + return false; + } + + //Test key size + if (key.Length > MCBlake2Module.MaxKeySize) + { + return false; + } + + //Compute one-shot hash + ERRNO result = MonoCypherLibrary.Shared.Blake2ComputeHmac(key, data, output); + + if (result < output.Length) + { + count = 0; + return false; + } + + count = output.Length; + return true; + } + } + } +} diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/IHashStream.cs b/lib/Hashing.Portable/src/Native/MonoCypher/IHashStream.cs new file mode 100644 index 0000000..29def42 --- /dev/null +++ b/lib/Hashing.Portable/src/Native/MonoCypher/IHashStream.cs @@ -0,0 +1,54 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: IHashStream.cs +* +* IHashStream.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.Native.MonoCypher +{ + /// + /// A base interface for a streaming (incremental) hash + /// function + /// + public interface IHashStream : IDisposable + { + /// + /// The configured hash size of this stream + /// + byte HashSize { get; } + + /// + /// Updates the hash of this stream with the specified message + /// + /// A reference to the first byte of the sequence + /// The size of the sequence + void Update(ref byte mRef, uint mSize); + + /// + /// Flushes the hash of this stream to the specified buffer + /// + /// A reference to the first byte in the output sequence + /// The size of the output sequence + void Flush(ref byte hashOut, byte hashSize); + } +} \ No newline at end of file diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/IHmacStream.cs b/lib/Hashing.Portable/src/Native/MonoCypher/IHmacStream.cs new file mode 100644 index 0000000..0ddc236 --- /dev/null +++ b/lib/Hashing.Portable/src/Native/MonoCypher/IHmacStream.cs @@ -0,0 +1,46 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: IHmacStream.cs +* +* IHmacStream.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/. +*/ + +namespace VNLib.Hashing.Native.MonoCypher +{ + /// + /// A base interface for a streaming (incremental) HMAC + /// authenticated hash function + /// + public interface IHmacStream: IHashStream + { + /// + /// The maximum key size allowed for this stream + /// + int MaxKeySize { get; } + + /// + /// Initializes this stream with the specified key + /// + /// A reference to the first byte of key data to import + /// The size of the key buffer + /// + void Initialize(ref byte key, byte keySize); + } +} \ No newline at end of file diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MCBlake2Module.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MCBlake2Module.cs new file mode 100644 index 0000000..e8f0156 --- /dev/null +++ b/lib/Hashing.Portable/src/Native/MonoCypher/MCBlake2Module.cs @@ -0,0 +1,420 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: MCBlake2Module.cs +* +* MCBlake2Module.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 System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using VNLib.Utils; +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; + +namespace VNLib.Hashing.Native.MonoCypher +{ + /// + /// Adds Blake2b hashing support to the + /// + public static unsafe class MCBlake2Module + { + [SafeMethodName("Blake2GetContextSize")] + internal delegate uint Blake2GetContextSize(); + + [SafeMethodName("Blake2Init")] + internal delegate int Blake2Init(IntPtr context, byte hashSize, void* key = null, uint keyLen = 0); + + [SafeMethodName("Blake2Update")] + internal delegate int Blake2Update(IntPtr context, void* data, uint dataLen); + + [SafeMethodName("Blake2Final")] + internal delegate int Blake2Final(IntPtr context, void* hash, uint hashLen); + + [SafeMethodName("Blake2GetHashSize")] + internal delegate uint Blake2GetHashSize(IntPtr context); + + + public const int MaxHashSize = 64; + public const int MaxKeySize = 64; + public const int MinSuggestedKDFHashSize = 32; + public const int MinSuggestedMACHashSize = 16; + + /// + /// Creates a new instance with the specified hash size + /// which must be between 1 and inclusive. + /// + /// + /// A hash size greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs. + /// + /// + /// + /// + /// + /// The hash size between 1 and inclusive + /// The heap to allocate the stream on + /// The initialzied instance + /// + /// + public static IHashStream Blake2CreateStream(this MonoCypherLibrary library, byte hashSize, IUnmangedHeap? heap) + { + _ = library ?? throw new ArgumentNullException(nameof(library)); + if(hashSize == 0 || hashSize > MaxHashSize) + { + throw new ArgumentOutOfRangeException(nameof(hashSize), $"The hash size must be between 1 and {MaxHashSize} inclusive"); + } + //Fall back to the shared heap if none is provided + heap ??= MemoryUtil.Shared; + + //Alloc stream and initialize it as a non-hmac stream + Blake2Stream stream = new(library, heap, hashSize); + try + { + //Initialize the stream + stream.Initialize(); + return stream; + } + catch + { + stream.Dispose(); + throw; + } + } + + /// + /// Creates a new keyed MAC instance with the specified hash size + /// and key which must be between 1 and inclusive. You must + /// initialize the instance before use, otherwise results are undefined. + /// + /// + /// A hash size greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs. + /// + /// + /// + /// + /// + /// The hash size between 1 and inclusive + /// The heap to allocate the stream on + /// + /// + public static IHmacStream Blake2CreateHmacStream(this MonoCypherLibrary library, byte hashSize, IUnmangedHeap? heap) + { + _ = library ?? throw new ArgumentNullException(nameof(library)); + if (hashSize == 0 || hashSize > MaxHashSize) + { + throw new ArgumentOutOfRangeException(nameof(hashSize), $"The hash size must be between 1 and {MaxHashSize} inclusive"); + } + //Fall back to the shared heap if none is provided + heap ??= MemoryUtil.Shared; + + //Return the raw stream, it will be initialized later + return new Blake2Stream(library, heap, hashSize); + } + + /// + /// Computes a Blake2b one-shot hash of the specified data and writes it to the variable-length output buffer. + /// The output buffer must be between 1 and inclusive. + /// + /// See for more information. + /// + /// + /// + /// The data buffer to compute the hash of + /// The hash output buffer + /// The number of bytes written to the output buffer or the error code from the native library + public static ERRNO Blake2ComputeHash(this MonoCypherLibrary library, ReadOnlySpan data, Span output) + { + ArgumentNullException.ThrowIfNull(library, nameof(library)); + + if(output.Length > MaxHashSize) + { + return ERRNO.E_FAIL; + } + + if(output.IsEmpty) + { + return ERRNO.E_FAIL; + } + + //Get context size + int contextSize = (int)library.Functions.Blake2GetContextSize(); + + //Allocate context on the stack + void* ctx = stackalloc byte[contextSize]; + + //Init context + if(library.Functions.Blake2Init((IntPtr)ctx, (byte)output.Length) != 0) + { + return ERRNO.E_FAIL; + } + + //initialize the context for the stream with hmac key + fixed (byte* keyPtr = &MemoryMarshal.GetReference(data), + hashPtr = &MemoryMarshal.GetReference(output) + ) + { + //Update with data + if(library.Functions.Blake2Update((IntPtr)ctx, keyPtr, (uint)data.Length) != 0) + { + return ERRNO.E_FAIL; + } + //Copy hash to output + if (library.Functions.Blake2Final((IntPtr)ctx, hashPtr, (uint)output.Length) != 0) + { + return ERRNO.E_FAIL; + } + } + + return output.Length; + } + + /// + /// Computes a Blake2b one-shot keyed MAC of the specified data and writes it to the variable-length output buffer. + /// The output buffer must be between 1 and inclusive. + /// The key must be between 1 and inclusive. + /// + /// See for more information. + /// + /// + /// + /// The HMAC key + /// The data buffer to compute the hash of + /// The hash output buffer + /// The number of bytes written to the output buffer or the error code from the native library + public static ERRNO Blake2ComputeHmac(this MonoCypherLibrary library, ReadOnlySpan key, ReadOnlySpan data, Span output) + { + ArgumentNullException.ThrowIfNull(library, nameof(library)); + + if (output.Length > MaxHashSize) + { + return ERR_HASH_LEN_INVLID; + } + + if (output.IsEmpty) + { + return ERR_NULL_PTR; + } + + if(key.IsEmpty) + { + return ERR_KEY_PTR_INVALID; + } + + if(key.Length > MaxKeySize) + { + return ERR_KEY_LEN_INVALID; + } + + //Get context size + int contextSize = (int)library.Functions.Blake2GetContextSize(); + + //Allocate context on the stack + void* ctx = stackalloc byte[contextSize]; + + fixed(byte* keyPtr = &MemoryMarshal.GetReference(key)) + { + //Init context with hmac parameters + if (library.Functions.Blake2Init((IntPtr)ctx, (byte)output.Length, keyPtr, (uint)key.Length) != 0) + { + return ERRNO.E_FAIL; + } + } + + //initialize the context for the stream with hmac key + fixed (byte* keyPtr = &MemoryMarshal.GetReference(data)) + { + //Update with data + if (library.Functions.Blake2Update((IntPtr)ctx, keyPtr, (uint)data.Length) != 0) + { + return ERRNO.E_FAIL; + } + } + + //Finalize/copy the current hash + fixed (byte* hashPtr = &MemoryMarshal.GetReference(output)) + { + if (library.Functions.Blake2Final((IntPtr)ctx, hashPtr, (uint)output.Length) != 0) + { + return ERRNO.E_FAIL; + } + } + + //Clear the context before returning + MemoryUtil.InitializeBlock((byte*)ctx, contextSize); + + return output.Length; + } + + const int ERR_NULL_PTR = -1; + const int ERR_HASH_LEN_INVLID = -16; + const int ERR_KEY_LEN_INVALID = -17; + const int ERR_KEY_PTR_INVALID = -18; + + private static void ThrowOnBlake2Error(int result) + { + switch (result) + { + //Success + case 0: + break; + //Null pointer + case ERR_NULL_PTR: + throw new ArgumentException("An illegal null pointer was passed to the function"); + //Invalid hash length + case ERR_HASH_LEN_INVLID: + throw new ArgumentOutOfRangeException("hashLen", "The hash length is invalid"); + //Invalid key length + case ERR_KEY_LEN_INVALID: + throw new ArgumentOutOfRangeException("keyLen", "The key length is invalid"); + + case ERR_KEY_PTR_INVALID: + throw new ArgumentException("The key pointer is null"); + + default: + throw new Exception($"An unknown error occured while hashing: {result}"); + + } + } + + private sealed class Blake2Stream : SafeHandle, IHashStream, IHmacStream + { + private readonly MonoCypherLibrary _library; + private readonly IUnmangedHeap _heap; + + /// + public override bool IsInvalid => handle == IntPtr.Zero; + + internal Blake2Stream(MonoCypherLibrary library, IUnmangedHeap heap, byte hashSize) :base(IntPtr.Zero, true) + { + Debug.Assert(hashSize > 0 && hashSize <= MaxHashSize, "Hash size must be between 1 and 64 inclusive"); + Debug.Assert(library != null, "Library argument passed to internal blake2 stream constructur is null"); + _library = library; + _heap = heap; + HashSize = hashSize; + } + + internal void Initialize() + { + //Make sure context is initialized + InitContextHandle(); + + //Init non-hmac + int initResult = _library.Functions.Blake2Init(handle, HashSize); + ThrowOnBlake2Error(initResult); + } + + /// + public byte HashSize { get; } + + /// + public int MaxKeySize => MaxKeySize; + + /// + public void Flush(ref byte hashOut, byte hashSize) + { + this.ThrowIfClosed(); + + if(Unsafe.IsNullRef(ref hashOut)) + { + throw new ArgumentNullException(nameof(hashOut)); + } + + //Guard for hash size + if(hashSize != HashSize) + { + throw new ArgumentException("The hash output must be the configured hash size", nameof(hashSize)); + } + //get the address of our context and the hash output reference + + fixed(byte* hashOutPtr = &hashOut) + { + _library.Functions.Blake2Final(handle, hashOutPtr, hashSize); + } + } + + /// + public void Initialize(ref byte key, byte keySize) + { + if (Unsafe.IsNullRef(ref key)) + { + throw new ArgumentNullException(nameof(key)); + } + + if(keySize > MaxKeySize) + { + throw new ArgumentOutOfRangeException(nameof(keySize), $"The key size must be between 1 and {MaxKeySize} inclusive bytes"); + } + + //Make sure context is initialized + InitContextHandle(); + + //initialize the context for the stream with hmac key + fixed (byte* keyPtr = &key) + { + int result = _library.Functions.Blake2Init(handle, HashSize, keyPtr, keySize); + ThrowOnBlake2Error(result); + } + } + + /// + public void Update(ref byte mRef, uint mSize) + { + this.ThrowIfClosed(); + + if (Unsafe.IsNullRef(ref mRef)) + { + throw new ArgumentNullException(nameof(mRef)); + } + + if (mSize == 0) + { + return; + } + + //get the address of the message reference + fixed (byte* message = &mRef) + { + int result = _library.Functions.Blake2Update(handle, message, mSize); + ThrowOnBlake2Error(result); + } + } + + private void InitContextHandle() + { + if (IsClosed) + { + throw new ObjectDisposedException(nameof(Blake2Stream)); + } + + //alloc buffer on the heap if not allocated + if (handle == IntPtr.Zero) + { + handle = _heap.Alloc(1, _library.Functions.Blake2GetContextSize(), true); + } + } + + /// + protected override bool ReleaseHandle() => _heap.Free(ref handle); + } + } +} \ No newline at end of file diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MCHashingStreamExtensions.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MCHashingStreamExtensions.cs new file mode 100644 index 0000000..3f27539 --- /dev/null +++ b/lib/Hashing.Portable/src/Native/MonoCypher/MCHashingStreamExtensions.cs @@ -0,0 +1,292 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: MCHashingStreamExtensions.cs +* +* MCHashingStreamExtensions.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 System.Buffers; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + + +namespace VNLib.Hashing.Native.MonoCypher +{ + /// + /// Provides extension methods for instances + /// + public static unsafe class MCHashingStreamExtensions + { + /// + /// Updates the hash of this stream with the specified message + /// + /// + /// A pointer to the message memory sequence + /// The size of the sequence + /// + /// + public static void Update(this IHashStream hashStream, IntPtr message, uint mSize) => Update(hashStream, message.ToPointer(), mSize); + + /// + /// Updates the hash of this stream with the specified message + /// + /// + /// A pointer to the message memory sequence + /// The size of the sequence + /// + /// + public static void Update(this IHashStream hashStream, void* message, uint mSize) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream)); + + //get ref from pointer + ref byte mRef = ref Unsafe.AsRef(message); + hashStream.Update(ref mRef, mSize); + } + + /// + /// Updates the hash of this stream with the specified message + /// + /// + /// The message memory sequence + /// + /// + public static void Update(this IHashStream hashStream, ReadOnlySpan message) + { + _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream)); + + if(message.Length == 0) + { + return; + } + + //Marshal reference to span + ref byte mRef = ref MemoryMarshal.GetReference(message); + hashStream.Update(ref mRef, (uint)message.Length); + } + + /// + /// Updates the hash of this stream with the specified message + /// + /// + /// The message memory sequence + /// + /// + public static void Update(this IHashStream hashStream, ReadOnlyMemory message) + { + _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream)); + if (message.Length == 0) + { + return; + } + + //Pin memory block instead of span marshalling + using MemoryHandle mHandle = message.Pin(); + + hashStream.Update(mHandle.Pointer, (uint)message.Length); + } + + /// + /// Writes the internal hash state to the specified memory location. The hash size + /// must be at least the size of + /// + /// + /// A pointer to the memory sequence to write the hash to + /// The size of the hash memory sequence, must be exactly + /// + /// + public static void Flush(this IHashStream hashStream, IntPtr hashOut, byte hashSize) => Flush(hashStream, hashOut.ToPointer(), hashSize); + + /// + /// Writes the internal hash state to the specified memory location. The hash size + /// must be at least the size of + /// + /// + /// A pointer to the memory sequence to write the hash to + /// The size of the hash memory sequence, must be exactly + /// + /// + public static void Flush(this IHashStream hashStream, void* hashOut, byte hashSize) + { + if (hashOut == null) + { + throw new ArgumentNullException(nameof(hashOut)); + } + _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream)); + + //get ref from pointer + ref byte hashOutRef = ref Unsafe.AsRef(hashOut); + hashStream.Flush(ref hashOutRef, hashSize); + } + + /// + /// Writes the internal hash state to the specified memory location. The hash size + /// must be at least the size of + /// + /// + /// The memory sequence to write the hash to, must be exactly + /// + /// + public static void Flush(this IHashStream hashStream, Span hashOut) + { + _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream)); + + if (hashOut.Length != hashStream.HashSize) + { + throw new ArgumentException("The hash output must be the configured hash size", nameof(hashOut)); + } + + //Marshal reference to span and flush + ref byte hashOutRef = ref MemoryMarshal.GetReference(hashOut); + hashStream.Flush(ref hashOutRef, (byte)hashOut.Length); + } + + /// + /// Writes the internal hash state to the specified memory location. The hash size + /// must be at least the size of + /// + /// + /// The memory sequence to write the hash to, must be exactly + /// + /// + public static void Flush(this IHashStream hashStream, Memory hashOut) + { + _ = hashStream ?? throw new ArgumentNullException(nameof(hashStream)); + + if (hashOut.Length != hashStream.HashSize) + { + throw new ArgumentException("The hash output must be the configured hash size", nameof(hashOut)); + } + + //Pin memory block instead of span marshalling + using MemoryHandle hashOutHandle = hashOut.Pin(); + hashStream.Flush(hashOutHandle.Pointer, (byte)hashOut.Length); + } + + /// + /// Creates a new keyed MAC instance with the specified hash size. + /// + /// + /// A value greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs. + /// + /// + /// + /// + /// + /// A pointer to the HMAC key buffer + /// The HMAC key must be between 1 and inclusive + /// + /// + public static void Initialize(this IHmacStream stream, IntPtr key, byte keySize) => Initialize(stream, (byte*)key.ToPointer(), keySize); + + /// + /// Creates a new keyed MAC instance with the specified hash size. + /// + /// + /// A value greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs. + /// + /// + /// + /// + /// + /// A pointer to the HMAC key buffer + /// The HMAC key must be between 1 and inclusive + /// + /// + public static void Initialize(this IHmacStream stream, byte* key, byte keySize) + { + _ = stream ?? throw new ArgumentNullException(nameof(stream)); + + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + if (keySize == 0 || keySize > stream.MaxKeySize) + { + throw new ArgumentOutOfRangeException(nameof(keySize), $"The key size must be between 1 and {stream.MaxKeySize} inclusive"); + } + + //Get reference to key + ref byte asRef = ref Unsafe.AsRef(key); + stream.Initialize(ref asRef, keySize); + } + + /// + /// Creates a new keyed MAC instance with the specified hash size. + /// + /// + /// A value greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs. + /// + /// + /// + /// + /// + /// The HMAC key buffer + /// + /// + public static void Initialize(this IHmacStream stream, ReadOnlySpan key) + { + _ = stream ?? throw new ArgumentNullException(nameof(stream)); + + if (key.Length > stream.MaxKeySize) + { + throw new ArgumentOutOfRangeException(nameof(key), $"The hash size must be less than or equal to {stream.MaxKeySize}"); + } + + //Get span ref and call interface method + ref byte asRef = ref MemoryMarshal.GetReference(key); + stream.Initialize(ref asRef, (byte)key.Length); + } + + /// + /// Creates a new keyed MAC instance with the specified hash size. + /// + /// + /// A value greater than 32 is recommended for KDFs and a value greater than 16 is recommended for MACs. + /// + /// + /// + /// + /// + /// The HMAC key buffer + /// + /// + public static void Initialize(this IHmacStream stream, ReadOnlyMemory key) + { + _ = stream ?? throw new ArgumentNullException(nameof(stream)); + + if (key.Length > stream.MaxKeySize) + { + throw new ArgumentOutOfRangeException(nameof(key), $"The hash size must be less than or equal to {stream.MaxKeySize}"); + } + + //If key is default, then h.Pointer will be null, which is handled + using MemoryHandle h = key.Pin(); + + //Get address of ref + Initialize(stream, (byte*)h.Pointer, (byte)key.Length); + } + } +} \ No newline at end of file diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MCPasswordModule.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MCPasswordModule.cs new file mode 100644 index 0000000..9dbcb41 --- /dev/null +++ b/lib/Hashing.Portable/src/Native/MonoCypher/MCPasswordModule.cs @@ -0,0 +1,159 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: MCPasswordModule.cs +* +* MCPasswordModule.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 System.Diagnostics; + +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; + +namespace VNLib.Hashing.Native.MonoCypher +{ + + /// + /// Provides argon2 hashing functionality using the MonoCypher library. + /// + /// + /// + /// + public static unsafe class MCPasswordModule + { + + const uint MC_ARGON2_WA_MEM_ALIGNMENT = sizeof(ulong); + const ulong MC_BLOCK_MULTIPLIER = 1024UL; + + [SafeMethodName("Argon2CalcWorkAreaSize")] + internal delegate uint Argon2CalcWorkArea(Argon2Context* context); + + [SafeMethodName("Argon2ComputeHash")] + internal delegate Argon2_ErrorCodes Argon2Hash(Argon2Context* context, void* workArea); + + + /// + /// Creates a new instance using the provided . + /// + /// + /// The heap to allocate internal buffers from + /// The wrapper instance + /// + public static IArgon2Library Argon2CreateLibrary(this MonoCypherLibrary Library, IUnmangedHeap heap) + { + //Validate arguments + _ = Library ?? throw new ArgumentNullException(nameof(Library)); + _ = heap ?? throw new ArgumentNullException(nameof(heap)); + return new Argon2HashLib(Library, heap); + } + + private static void Hash(this MonoCypherLibrary library, IUnmangedHeap heap, Argon2Context* context) + { + _ = library ?? throw new ArgumentNullException(nameof(library)); + _ = heap ?? throw new ArgumentNullException(nameof(heap)); + + //Validate context + ValidateContext(context); + + CalcWorkAreaSize(context, out uint elements, out uint alignment); + + //Allocate work area + IntPtr workArea = heap.Alloc(elements, alignment, true); + + try + { + //Compute the hash using the allocated work area + Argon2_ErrorCodes result = library.Functions.Argon2Hash(context, workArea.ToPointer()); + VnArgon2.ThrowOnArgonErr(result); + } + finally + { + //Free work area + bool free = heap.Free(ref workArea); + Debug.Assert(free, "Failed to free work area pointer after allocation"); + } + } + + /* + * Since unmanaged heaps are being utilized and they support alignment args, we can compute + * a proper alignment value and element count for the work area that best matches the native + * impl. + * + * Currently the blocks are broken into a struct array of 128 u64 elements. So currently + * using sizeof(ulong) as the alignment value so we can pass that to the heap for better + * alignment + */ + + private static void CalcWorkAreaSize(Argon2Context* ctx, out uint elements, out uint alignment) + { + ulong mCost = ctx->m_cost; + ulong size = mCost * MC_BLOCK_MULTIPLIER; + + //Calculate element size after alignment + elements = checked((uint)(size / MC_ARGON2_WA_MEM_ALIGNMENT)); + alignment = MC_ARGON2_WA_MEM_ALIGNMENT; + + //Sanity check + Debug.Assert(((ulong)elements * (ulong)alignment) == size); + } + + private static void ValidateContext(Argon2Context* context) + { + if(context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context->outptr == null || context->outlen == 0) + { + throw new ArgumentException("Output buffer is null or empty"); + } + + if (context->pwd == null || context->pwdlen == 0) + { + throw new ArgumentException("Password buffer is null or empty"); + } + + if (context->salt == null || context->saltlen == 0) + { + throw new ArgumentException("Salt buffer is null or empty"); + } + } + + private sealed record class Argon2HashLib(MonoCypherLibrary Library, IUnmangedHeap BufferHeap) : IArgon2Library + { + + /// + public int Argon2Hash(IntPtr context) + { + if(context == IntPtr.Zero) + { + throw new ArgumentNullException(nameof(context), ""); + } + + //Invoke hash with argon2 context pointer + Hash(Library, BufferHeap, (Argon2Context*)context); + return 0; + } + } + } + +} \ No newline at end of file diff --git a/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs b/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs new file mode 100644 index 0000000..ff517ef --- /dev/null +++ b/lib/Hashing.Portable/src/Native/MonoCypher/MonoCypherLibrary.cs @@ -0,0 +1,169 @@ +/* +* Copyright (c) 2023 Vaughn Nugent +* +* Library: VNLib +* Package: VNLib.Hashing.Portable +* File: MonoCypherLibrary.cs +* +* MonoCypherLibrary.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 System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +using VNLib.Utils; +using VNLib.Utils.Native; +using VNLib.Utils.Extensions; + +namespace VNLib.Hashing.Native.MonoCypher +{ + + /// + /// Wraps a safe library handle to the MonoCypher library and + /// provides access to the MonoCypher functions + /// + public unsafe class MonoCypherLibrary : VnDisposeable + { + public const string MONOCYPHER_LIB_ENVIRONMENT_VAR_NAME = "VNLIB_MONOCYPHER_DLL_PATH"; + public const string MONOCYPHER_LIB_DEFAULT_NAME = "vnlib_monocypher"; + + /// + /// Determines if the default MonoCypher library can be loaded into + /// the current process. + /// + /// true if the user enabled the default library, false otherwise + public static bool CanLoadDefaultLibrary() => string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(MONOCYPHER_LIB_ENVIRONMENT_VAR_NAME)) == false; + + private static readonly Lazy _defaultLib = new (LoadDefaultLibraryInternal, LazyThreadSafetyMode.PublicationOnly); + + /// + /// Gets the default MonoCypher library for the current process + /// + /// You should call before accessing + /// this property to ensure that the default library can be loaded + /// + /// + public static MonoCypherLibrary Shared => _defaultLib.Value; + + /// + /// Loads a new instance of the MonoCypher library with environment defaults + /// + /// You should call before calling this + /// function + /// + /// + /// The new library instance + /// + /// + public static MonoCypherLibrary LoadNewInstance() => LoadDefaultLibraryInternal(); + + /// + /// Loads the MonoCypher library from the specified shared library path + /// + /// The file path or library name to search for + /// The directory search flags + /// The new instance + public static MonoCypherLibrary LoadLibrary(string path, DllImportSearchPath searchPath) + { + SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(path, searchPath); + return new(lib, true); + } + + private static MonoCypherLibrary LoadDefaultLibraryInternal() + { + //Get the path to the library or default + string? monoCypherLibPath = Environment.GetEnvironmentVariable(MONOCYPHER_LIB_ENVIRONMENT_VAR_NAME) ?? MONOCYPHER_LIB_DEFAULT_NAME; + + Trace.WriteLine("Attempting to load global native MonoCypher library from: " + monoCypherLibPath, "MonoCypher"); + + SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(monoCypherLibPath, DllImportSearchPath.SafeDirectories); + return new(lib, true); + } + + + private readonly SafeLibraryHandle _library; + private readonly FunctionTable _functions; + private readonly bool _ownsHandle; + + internal ref readonly FunctionTable Functions + { + get + { + Check(); + return ref _functions; + } + } + + /// + /// Creates a new instance of consuming the + /// specified library handle + /// + /// The safe MonoCypher library handle + /// A value that indicates if the current instance owns the library handle + /// + public MonoCypherLibrary(SafeLibraryHandle library, bool ownsHandle) + { + _library = library ?? throw new ArgumentNullException(nameof(library)); + _ownsHandle = ownsHandle; + + //Init the function table + InitFunctionTable(library, out _functions); + } + + private static void InitFunctionTable(SafeLibraryHandle library, out FunctionTable functions) + { + functions = new FunctionTable + { + //Argon2 + Argon2Hash = library.DangerousGetMethod(), + Argon2CalcWorkArea = library.DangerousGetMethod(), + + //Blake2b + Blake2GetContextSize = library.DangerousGetMethod(), + Blake2Init = library.DangerousGetMethod(), + Blake2Update = library.DangerousGetMethod(), + Blake2Final = library.DangerousGetMethod(), + Blake2GethashSize = library.DangerousGetMethod(), + }; + } + + /// + protected override void Free() + { + if(_ownsHandle) + { + _library.Dispose(); + } + } + + internal readonly struct FunctionTable + { + //Argon2 module + public readonly MCPasswordModule.Argon2Hash Argon2Hash { get; init; } + public readonly MCPasswordModule.Argon2CalcWorkArea Argon2CalcWorkArea { get; init; } + + //Blake2 module + public readonly MCBlake2Module.Blake2GetContextSize Blake2GetContextSize { get; init; } + public readonly MCBlake2Module.Blake2Init Blake2Init { get; init; } + public readonly MCBlake2Module.Blake2Update Blake2Update { get; init; } + public readonly MCBlake2Module.Blake2Final Blake2Final { get; init; } + public readonly MCBlake2Module.Blake2GetHashSize Blake2GethashSize { get; init; } + } + } +} \ No newline at end of file -- cgit