From 1b590c2517fef110564943ed8a10edd11fa758b0 Mon Sep 17 00:00:00 2001 From: vnugent Date: Wed, 22 May 2024 17:49:57 -0400 Subject: Squashed commit of the following: commit 9a835fe12c9586ab8dd44d7c96fef4a2d6017e4b Author: vnugent Date: Fri May 17 18:27:03 2024 -0400 chore: Update mimmaloc v2.1.6, update fPIC & cleanup commit 3b7004b88acfc7f7baa3a8857a5a2f7cf3dd560e Author: vnugent Date: Fri May 17 16:03:28 2024 -0400 feat: Added ReadFileDataAsync function commit 9a964795757bf0da4dd7fcab15ad304f4ea3fdf1 Author: vnugent Date: Wed May 15 21:57:39 2024 -0400 refactor: Harden some argon2 password hashing commit 4035c838c1508af0aa7e767a97431a692958ce1c Author: vnugent Date: Sun May 12 16:55:32 2024 -0400 perf: Utils + http perf mods commit f4f0d4f74250257991c57bfae74c4852c7e1ae46 Author: vnugent Date: Thu May 2 15:22:53 2024 -0400 feat: Buff middleware handlers | | Added implicit support for middleware post processing of files before the filehandler closes the connection. Also cleaned up some project file stuff commit f0b7dca107659df1d7d4631fdbd2aae9d716d053 Merge: 8c4a45e 107b058 Author: vnugent Date: Sat Apr 20 12:24:05 2024 -0400 Merge branch 'main' into develop commit 8c4a45e384accf92b1b6d748530e8d46f7de40d6 Author: vnugent Date: Sat Apr 20 11:10:30 2024 -0400 refactor: Overhaul C libraries and fix builds commit 42ff77080d10b0fc9fecbbc46141e8e23a1d066a Author: vnugent Date: Sat Apr 20 00:45:57 2024 -0400 fix!: Middlware array, multiple cookie set, and cookie check commit 97e82b9d66f387f9e6d21d88ddc7a8ab8693149c Merge: 4ca5791 e07537a Author: vnugent Date: Tue Apr 2 13:34:22 2024 -0400 Merge branch 'main' into develop commit 4ca5791ed67b9834bdbd010206b30373e4705e9b Author: vnugent Date: Tue Apr 2 13:32:12 2024 -0400 fix: Missed ! on null pointer check commit 9b4036377c52200c6488c98180d69a0e63321f97 Author: vnugent Date: Tue Apr 2 13:22:29 2024 -0400 fix: Fix _In_ macro for compression public api commit 53a7b4b5c5b67b4a4e06e1d9098cac4bcd6afd7c Merge: 448a93b 21130c8 Author: vnugent Date: Sun Mar 31 17:01:15 2024 -0400 Merge branch 'main' into develop commit 448a93bb1d18d032087afe2476ffccb98634a54c Author: vnugent Date: Sun Mar 31 16:56:51 2024 -0400 ci: fix third-party dir cleanup commit 9afed1427472da1ea13079f98dbe27339e55ee7d Author: vnugent Date: Sun Mar 31 16:43:15 2024 -0400 perf: Deprecate unsafememoryhandle span extensions commit 3ff90da4f02af47ea6d233fdd4445337ebe36452 Author: vnugent Date: Sat Mar 30 21:36:18 2024 -0400 refactor: Updates, advanced tracing, http optimizations commit 8d6b79b5ae309b36f265ba81529bcef8bfcd7414 Merge: 6c1667b 5585915 Author: vnugent Date: Sun Mar 24 21:01:31 2024 -0400 Merge branch 'main' into develop commit 6c1667be23597513537f8190e2f55d65eb9b7c7a Author: vnugent Date: Fri Mar 22 12:01:53 2024 -0400 refactor: Overhauled native library loading and lazy init commit ebf688f2f974295beabf7b5def7e6f6f150551d0 Author: vnugent Date: Wed Mar 20 22:16:17 2024 -0400 refactor: Update compression header files and macros + Ci build commit 9c7b564911080ccd5cbbb9851a0757b05e1e9047 Author: vnugent Date: Tue Mar 19 21:54:49 2024 -0400 refactor: JWK overhaul & add length getter to FileUpload commit 6d8c3444e09561e5957491b3cc1ae858e0abdd14 Author: vnugent Date: Mon Mar 18 16:13:20 2024 -0400 feat: Add FNV1a software checksum and basic correction tests commit 00d182088cecefc08ca80b1faee9bed3f215f40b Author: vnugent Date: Fri Mar 15 01:05:27 2024 -0400 chore: #6 Use utils filewatcher instead of built-in commit d513c10d9895c6693519ef1d459c6a5a76929436 Author: vnugent Date: Sun Mar 10 21:58:14 2024 -0400 source tree project location updated --- .../src/Argon2/Argon2PasswordEntry.cs | 45 ++-- lib/Hashing.Portable/src/Argon2/VnArgon2.cs | 250 ++++++++++++--------- .../src/VNLib.Hashing.Portable.csproj | 11 +- 3 files changed, 169 insertions(+), 137 deletions(-) (limited to 'lib/Hashing.Portable/src') diff --git a/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs b/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs index f99d0dc..e195526 100644 --- a/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs +++ b/lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs @@ -30,57 +30,64 @@ using VNLib.Utils.Extensions; namespace VNLib.Hashing { - internal readonly ref struct Argon2PasswordEntry + internal readonly ref struct Argon2PasswordEntry(ReadOnlySpan str) { - private readonly ReadOnlySpan _window; + private readonly ReadOnlySpan _window = str; - public readonly Argon2Version Version; - public readonly ReadOnlySpan Salt; - public readonly ReadOnlySpan Hash; + public readonly Argon2Version Version = ParseVersion(str); + public readonly ReadOnlySpan Salt = ParseSalt(str); + public readonly ReadOnlySpan Hash = ParseHash(str); private static Argon2Version ParseVersion(ReadOnlySpan window) { //Version comes after the v= prefix ReadOnlySpan v = window.SliceAfterParam("v="); - v = v.SliceBeforeParam(','); //Parse the version as an enum value - return Enum.Parse(v); + return Enum.Parse(v.SliceBeforeParam(',')); } private static uint ParseTimeCost(ReadOnlySpan window) { //TimeCost comes after the t= prefix ReadOnlySpan t = window.SliceAfterParam("t="); - t = t.SliceBeforeParam(','); //Parse the time cost as an unsigned integer - return uint.Parse(t, NumberStyles.Integer, CultureInfo.InvariantCulture); + return uint.Parse( + t.SliceBeforeParam(','), + NumberStyles.Integer, + CultureInfo.InvariantCulture + ); } private static uint ParseMemoryCost(ReadOnlySpan window) { //MemoryCost comes after the m= prefix ReadOnlySpan m = window.SliceAfterParam("m="); - m = m.SliceBeforeParam(','); //Parse the memory cost as an unsigned integer - return uint.Parse(m, NumberStyles.Integer, CultureInfo.InvariantCulture); + return uint.Parse( + m.SliceBeforeParam(','), + NumberStyles.Integer, + CultureInfo.InvariantCulture + ); } private static uint ParseParallelism(ReadOnlySpan window) { //Parallelism comes after the p= prefix ReadOnlySpan p = window.SliceAfterParam("p="); - p = p.SliceBeforeParam(','); //Parse the parallelism as an unsigned integer - return uint.Parse(p, NumberStyles.Integer, CultureInfo.InvariantCulture); + return uint.Parse( + p.SliceBeforeParam(','), + NumberStyles.Integer, + CultureInfo.InvariantCulture + ); } private static ReadOnlySpan ParseSalt(ReadOnlySpan window) { //Salt comes after the s= prefix ReadOnlySpan s = window.SliceAfterParam("s="); - s = s.SliceBeforeParam('$'); //Parse the salt as a string - return s; + return s.SliceBeforeParam('$'); } private static ReadOnlySpan ParseHash(ReadOnlySpan window) @@ -90,14 +97,6 @@ namespace VNLib.Hashing return window[(start + 1)..]; } - public Argon2PasswordEntry(ReadOnlySpan str) - { - _window = str; - Version = ParseVersion(str); - Salt = ParseSalt(str); - Hash = ParseHash(str); - } - public readonly Argon2CostParams GetCostParams() { return new() diff --git a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs index b88c232..d557822 100644 --- a/lib/Hashing.Portable/src/Argon2/VnArgon2.cs +++ b/lib/Hashing.Portable/src/Argon2/VnArgon2.cs @@ -35,6 +35,14 @@ using VNLib.Utils.Extensions; using VNLib.Utils.Resources; using VNLib.Hashing.Native.MonoCypher; +/* + * Some stuff to note + * + * Functions have explicit parameters to avoid accidental buffer mixup + * when calling nested/overload functions. Please keep it that way for now + * I really want to avoid a whoopsie in password hasing. + */ + namespace VNLib.Hashing { @@ -78,9 +86,8 @@ namespace VNLib.Hashing 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); + + return LoadCustomLibrary(argon2EnvPath, DllImportSearchPath.SafeDirectories); } } @@ -131,27 +138,31 @@ namespace VNLib.Hashing { //Get bytes count int saltbytes = LocEncoding.GetByteCount(salt); - - //Get bytes count for password int passBytes = LocEncoding.GetByteCount(password); //Alloc memory for salt - using MemoryHandle buffer = PwHeap.Alloc(saltbytes + passBytes); + using MemoryHandle buffer = PwHeap.Alloc(MemoryUtil.NearestPage(saltbytes + passBytes)); Span saltBuffer = buffer.AsSpan(0, saltbytes); Span passBuffer = buffer.AsSpan(saltbytes, passBytes); - //Encode salt with span the same size of the salt + //Decode from character buffers to binary buffers using default string encoding _ = LocEncoding.GetBytes(salt, saltBuffer); - - //Encode password, create a new span to make sure its proper size _ = LocEncoding.GetBytes(password, passBuffer); - - //Hash - string result = Hash2id(lib, passBuffer, saltBuffer, secret, in costParams, hashLen); - - //Zero buffer - MemoryUtil.InitializeBlock(ref buffer.GetReference(), buffer.GetIntLength()); + + string result = Hash2id( + lib: lib, + password: passBuffer, + salt: saltBuffer, + secret: secret, + costParams: in costParams, + hashLen: hashLen + ); + + MemoryUtil.InitializeBlock( + ref buffer.GetReference(), + buffer.GetIntLength() + ); return result; } @@ -180,17 +191,26 @@ namespace VNLib.Hashing //Get bytes count int passBytes = LocEncoding.GetByteCount(password); - //Alloc memory for password - using MemoryHandle pwdHandle = PwHeap.Alloc(passBytes); + //Alloc memory for password, round to page size again + using MemoryHandle pwdHandle = PwHeap.Alloc(MemoryUtil.NearestPage(passBytes)); //Encode password, create a new span to make sure its proper size _ = LocEncoding.GetBytes(password, pwdHandle.Span); - - //Hash - string result = Hash2id(lib, pwdHandle.Span, salt, secret, in costParams, hashLen); + + string result = Hash2id( + lib: lib, + password: pwdHandle.AsSpan(0, passBytes), //Only actuall size for decoding + salt: salt, + secret: secret, + costParams: in costParams, + hashLen: hashLen + ); //Zero buffer - MemoryUtil.InitializeBlock(ref pwdHandle.GetReference(), pwdHandle.GetIntLength()); + MemoryUtil.InitializeBlock( + ref pwdHandle.GetReference(), + pwdHandle.GetIntLength() + ); return result; } @@ -217,16 +237,26 @@ namespace VNLib.Hashing ) { string hash, salts; - //Alloc data for hash output - using IMemoryHandle hashHandle = PwHeap.Alloc(hashLen, true); - - //hash the password - Hash2id(lib, password, salt, secret, hashHandle.Span, in costParams); - //Encode hash - hash = Convert.ToBase64String(hashHandle.Span); - - //encode salt + /* + * Alloc a buffer of the nearest page to help disguise password related + * allocations. Global zero is always set on PwHeap. + */ + using MemoryHandle outputHandle = PwHeap.Alloc(MemoryUtil.NearestPage(hashLen)); + + //Trim buffer to exact hash size as it will likely be larger due to page alignment + Span outBuffer = outputHandle.AsSpan(0, checked((int)hashLen)); + + Hash2id( + lib: lib, + password: password, + salt: salt, + secret: secret, + rawHashOutput: outBuffer, + costParams: in costParams + ); + + hash = Convert.ToBase64String(outBuffer); salts = Convert.ToBase64String(salt); //Encode salt in base64 @@ -253,37 +283,36 @@ namespace VNLib.Hashing in Argon2CostParams costParams ) { - fixed (byte* pwd = password, slptr = salt, secretptr = secret, outPtr = rawHashOutput) + //Setup context + Argon2Context ctx = new() { - //Setup context - Argon2Context ctx; - //Pointer - Argon2Context* context = &ctx; - context->version = Argon2Version.Argon2DefaultVersion; - context->t_cost = costParams.TimeCost; - context->m_cost = costParams.MemoryCost; - context->threads = costParams.Parallelism; - context->lanes = costParams.Parallelism; - //Default flags - context->flags = ARGON2_DEFAULT_FLAGS; - context->allocate_cbk = null; - context->free_cbk = null; - //Password - context->pwd = pwd; - context->pwdlen = (uint)password.Length; - //Salt - context->salt = slptr; - context->saltlen = (uint)salt.Length; - //Secret - context->secret = secretptr; - context->secretlen = (uint)secret.Length; - //Output - context->outptr = outPtr; - context->outlen = (uint)rawHashOutput.Length; - //Hash - Argon2_ErrorCodes result = (Argon2_ErrorCodes)lib.Argon2Hash((IntPtr)context); - //Throw exceptions if error - ThrowOnArgonErr(result); + version = Argon2Version.Argon2DefaultVersion, + t_cost = costParams.TimeCost, + m_cost = costParams.MemoryCost, + threads = costParams.Parallelism, + lanes = costParams.Parallelism, + flags = ARGON2_DEFAULT_FLAGS, + allocate_cbk = null, + free_cbk = null, + }; + + fixed (byte* pSecret = secret, pPass = password, pSalt = salt, pRawHash = rawHashOutput) + { + ctx.pwd = pPass; + ctx.pwdlen = (uint)password.Length; + + ctx.salt = pSalt; + ctx.saltlen = (uint)salt.Length; + + ctx.secret = pSecret; + ctx.secretlen = (uint)secret.Length; + + ctx.outptr = pRawHash; + ctx.outlen = (uint)rawHashOutput.Length; //Clamp to actual desired hash size + + Argon2_ErrorCodes argResult = (Argon2_ErrorCodes)lib.Argon2Hash(new IntPtr(&ctx)); + //Throw an excpetion if an error ocurred + ThrowOnArgonErr(argResult); } } @@ -332,16 +361,20 @@ namespace VNLib.Hashing int saltBase64BufSize = Base64.GetMaxDecodedFromUtf8Length(entry.Salt.Length); int rawPassLen = LocEncoding.GetByteCount(rawPass); - //Alloc buffer for decoded data - using MemoryHandle rawBufferHandle = PwHeap.Alloc(passBase64BufSize + saltBase64BufSize + rawPassLen); + /* + * Alloc binary buffer for decoding. Align it to the nearest page again + * to help disguise the allocation purpose. + */ + nint nearestPage = MemoryUtil.NearestPage(passBase64BufSize + saltBase64BufSize + rawPassLen); + using MemoryHandle rawBufferHandle = PwHeap.Alloc(nearestPage); //Split buffers - Span saltBuf = rawBufferHandle.Span[..saltBase64BufSize]; + Span saltBuf = rawBufferHandle.AsSpan(0, saltBase64BufSize); Span passBuf = rawBufferHandle.AsSpan(saltBase64BufSize, passBase64BufSize); Span rawPassBuf = rawBufferHandle.AsSpan(saltBase64BufSize + passBase64BufSize, rawPassLen); + //Decode hash { - //Decode salt if (!Convert.TryFromBase64Chars(entry.Hash, passBuf, out int actualHashLen)) { throw new VnArgon2PasswordFormatException("Failed to recover hash bytes"); @@ -362,11 +395,21 @@ namespace VNLib.Hashing //encode password bytes rawPassLen = LocEncoding.GetBytes(rawPass, rawPassBuf); - //Verify password - bool result = Verify2id(lib, rawPassBuf[..rawPassLen], saltBuf, secret, passBuf, in costParams); - //Zero buffer - MemoryUtil.InitializeBlock(ref rawBufferHandle.GetReference(), rawBufferHandle.GetIntLength()); + bool result = Verify2id( + lib: lib, + rawPass: rawPassBuf[..rawPassLen], + salt: saltBuf, + secret: secret, + hashBytes: passBuf, + costParams: in costParams + ); + + //Zero entire buffer + MemoryUtil.InitializeBlock( + ref rawBufferHandle.GetReference(), + rawBufferHandle.GetIntLength() + ); return result; } @@ -395,48 +438,35 @@ namespace VNLib.Hashing in Argon2CostParams costParams ) { - //Alloc data for hash output - using MemoryHandle outputHandle = PwHeap.Alloc(hashBytes.Length); - - //Get pointers - fixed (byte* secretptr = secret, pwd = rawPass, slptr = salt) - { - //Setup context - Argon2Context ctx; - //Pointer - Argon2Context* context = &ctx; - context->version = Argon2Version.Argon2DefaultVersion; - context->t_cost = costParams.TimeCost; - context->m_cost = costParams.MemoryCost; - context->threads = costParams.Parallelism; - context->lanes = costParams.Parallelism; - //Default flags - context->flags = ARGON2_DEFAULT_FLAGS; - //Use default memory allocator - context->allocate_cbk = null; - context->free_cbk = null; - //Password - context->pwd = pwd; - context->pwdlen = (uint)rawPass.Length; - //Salt - context->salt = slptr; - context->saltlen = (uint)salt.Length; - //Secret - context->secret = secretptr; - context->secretlen = (uint)secret.Length; - //Output - context->outptr = outputHandle.Base; - context->outlen = (uint)outputHandle.Length; - //Hash - Argon2_ErrorCodes argResult = (Argon2_ErrorCodes)lib.Argon2Hash((IntPtr)context); - //Throw an excpetion if an error ocurred - ThrowOnArgonErr(argResult); - } - //Return the comparison - bool result = CryptographicOperations.FixedTimeEquals(outputHandle.Span, hashBytes); - - //Zero buffer - MemoryUtil.InitializeBlock(ref outputHandle.GetReference(), outputHandle.GetIntLength()); + /* + * Alloc a buffer of the nearest page to help disguise password related + * allocations. Global zero is always set on PwHeap. + */ + using MemoryHandle outputHandle = PwHeap.Alloc(MemoryUtil.NearestPage(hashBytes.Length)); + + //Trim buffer to exact hash size as it will likely be larger due to page alignment + Span outBuffer = outputHandle.AsSpan(0, hashBytes.Length); + + /* + * Verification works by computing the hash of the input and comparing it + * to the existing one. Hash functions are one-way by design, so now you know :) + */ + + Hash2id( + lib: lib, + password: rawPass, + salt: salt, + secret: secret, + rawHashOutput: outBuffer, + costParams: in costParams + ); + + bool result = CryptographicOperations.FixedTimeEquals(outBuffer, hashBytes); + + MemoryUtil.InitializeBlock( + ref outputHandle.GetReference(), + outputHandle.GetIntLength() + ); return result; } diff --git a/lib/Hashing.Portable/src/VNLib.Hashing.Portable.csproj b/lib/Hashing.Portable/src/VNLib.Hashing.Portable.csproj index 7e31e0c..da61ae1 100644 --- a/lib/Hashing.Portable/src/VNLib.Hashing.Portable.csproj +++ b/lib/Hashing.Portable/src/VNLib.Hashing.Portable.csproj @@ -1,17 +1,20 @@  - net8.0 + net8.0 + enable VNLib.Hashing.Portable VNLib.Hashing - True - enable - latest-all + True True True false + + latest-all + + VNLib Hashing Function/Alg Library Provides managed and random cryptocraphic hashing helper classes, including complete Argon2 password hashing. -- cgit