diff options
author | vnugent <public@vaughnnugent.com> | 2024-05-15 21:57:39 -0400 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-05-15 21:57:39 -0400 |
commit | 9a964795757bf0da4dd7fcab15ad304f4ea3fdf1 (patch) | |
tree | 027eaca81d744eaa438f0f25c680848974c549e8 /lib | |
parent | 4035c838c1508af0aa7e767a97431a692958ce1c (diff) |
refactor: Harden some argon2 password hashing
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs | 45 | ||||
-rw-r--r-- | lib/Hashing.Portable/src/Argon2/VnArgon2.cs | 250 | ||||
-rw-r--r-- | lib/Hashing.Portable/tests/ManagedHashTests.cs | 27 | ||||
-rw-r--r-- | lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs | 100 | ||||
-rw-r--r-- | lib/Utils/src/Memory/IUnmangedHeap.cs | 7 | ||||
-rw-r--r-- | lib/Utils/src/Memory/UnmanagedHeapBase.cs | 2 |
6 files changed, 251 insertions, 180 deletions
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<char> str) { - private readonly ReadOnlySpan<char> _window; + private readonly ReadOnlySpan<char> _window = str; - public readonly Argon2Version Version; - public readonly ReadOnlySpan<char> Salt; - public readonly ReadOnlySpan<char> Hash; + public readonly Argon2Version Version = ParseVersion(str); + public readonly ReadOnlySpan<char> Salt = ParseSalt(str); + public readonly ReadOnlySpan<char> Hash = ParseHash(str); private static Argon2Version ParseVersion(ReadOnlySpan<char> window) { //Version comes after the v= prefix ReadOnlySpan<char> v = window.SliceAfterParam("v="); - v = v.SliceBeforeParam(','); //Parse the version as an enum value - return Enum.Parse<Argon2Version>(v); + return Enum.Parse<Argon2Version>(v.SliceBeforeParam(',')); } private static uint ParseTimeCost(ReadOnlySpan<char> window) { //TimeCost comes after the t= prefix ReadOnlySpan<char> 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<char> window) { //MemoryCost comes after the m= prefix ReadOnlySpan<char> 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<char> window) { //Parallelism comes after the p= prefix ReadOnlySpan<char> 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<char> ParseSalt(ReadOnlySpan<char> window) { //Salt comes after the s= prefix ReadOnlySpan<char> s = window.SliceAfterParam("s="); - s = s.SliceBeforeParam('$'); //Parse the salt as a string - return s; + return s.SliceBeforeParam('$'); } private static ReadOnlySpan<char> ParseHash(ReadOnlySpan<char> window) @@ -90,14 +97,6 @@ namespace VNLib.Hashing return window[(start + 1)..]; } - public Argon2PasswordEntry(ReadOnlySpan<char> 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<byte> buffer = PwHeap.Alloc<byte>(saltbytes + passBytes); + using MemoryHandle<byte> buffer = PwHeap.Alloc<byte>(MemoryUtil.NearestPage(saltbytes + passBytes)); Span<byte> saltBuffer = buffer.AsSpan(0, saltbytes); Span<byte> 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<byte> pwdHandle = PwHeap.Alloc<byte>(passBytes); + //Alloc memory for password, round to page size again + using MemoryHandle<byte> pwdHandle = PwHeap.Alloc<byte>(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<byte> hashHandle = PwHeap.Alloc<byte>(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<byte> outputHandle = PwHeap.Alloc<byte>(MemoryUtil.NearestPage(hashLen)); + + //Trim buffer to exact hash size as it will likely be larger due to page alignment + Span<byte> 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<byte> rawBufferHandle = PwHeap.Alloc<byte>(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<byte> rawBufferHandle = PwHeap.Alloc<byte>(nearestPage); //Split buffers - Span<byte> saltBuf = rawBufferHandle.Span[..saltBase64BufSize]; + Span<byte> saltBuf = rawBufferHandle.AsSpan(0, saltBase64BufSize); Span<byte> passBuf = rawBufferHandle.AsSpan(saltBase64BufSize, passBase64BufSize); Span<byte> 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<byte> outputHandle = PwHeap.Alloc<byte>(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<byte> outputHandle = PwHeap.Alloc<byte>(MemoryUtil.NearestPage(hashBytes.Length)); + + //Trim buffer to exact hash size as it will likely be larger due to page alignment + Span<byte> 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/tests/ManagedHashTests.cs b/lib/Hashing.Portable/tests/ManagedHashTests.cs index 3bcdb33..8657290 100644 --- a/lib/Hashing.Portable/tests/ManagedHashTests.cs +++ b/lib/Hashing.Portable/tests/ManagedHashTests.cs @@ -8,6 +8,7 @@ using VNLib.Utils.Memory; namespace VNLib.Hashing.Tests { + [TestClass()] public class ManagedHashTests { @@ -30,27 +31,27 @@ namespace VNLib.Hashing.Tests //Compute hash ERRNO hashSize = ManagedHash.ComputeHash(testData, heapBuffer.Span, alg); - Assert.IsTrue(hashSize == Math.Abs(hashSize)); + Assert.IsTrue(hashSize == ManagedHash.GetHashSize(alg)); //With input string and heap buffer hashSize = ManagedHash.ComputeHash("test", heapBuffer.Span, alg); - Assert.IsTrue(hashSize == Math.Abs(hashSize)); + Assert.IsTrue(hashSize == ManagedHash.GetHashSize(alg)); //Compute string and byte array byte[] testdata = ManagedHash.ComputeHash(testData, alg); - Assert.IsTrue(testdata.Length == Math.Abs(hashSize)); + Assert.IsTrue(testdata.Length == ManagedHash.GetHashSize(alg)); //With input string testdata = ManagedHash.ComputeHash("test", alg); - Assert.IsTrue(testdata.Length == Math.Abs(hashSize)); + Assert.IsTrue(testdata.Length == ManagedHash.GetHashSize(alg)); - //Compute hash as string + //Compute hash as a hex string. Output should be 2x because hex is 2 chars per byte string testEnc = ManagedHash.ComputeHash(testdata, alg, HashEncodingMode.Hexadecimal); - Assert.IsTrue(testEnc.Length == Math.Abs(hashSize) * 2); + Assert.IsTrue(testEnc.Length == ManagedHash.GetHashSize(alg) * 2); //With input string testEnc = ManagedHash.ComputeHash("test", alg, HashEncodingMode.Hexadecimal); - Assert.IsTrue(testEnc.Length == Math.Abs(hashSize) * 2); + Assert.IsTrue(testEnc.Length == ManagedHash.GetHashSize(alg) * 2); } } @@ -74,27 +75,27 @@ namespace VNLib.Hashing.Tests //Compute hash ERRNO hashSize = ManagedHash.ComputeHmac(testKey, testData, heapBuffer.Span, alg); - Assert.IsTrue(hashSize == Math.Abs(hashSize)); + Assert.IsTrue(hashSize == ManagedHash.GetHashSize(alg)); //With input string and heap buffer hashSize = ManagedHash.ComputeHmac(testKey, "test", heapBuffer.Span, alg); - Assert.IsTrue(hashSize == Math.Abs(hashSize)); + Assert.IsTrue(hashSize == ManagedHash.GetHashSize(alg)); //Compute string and byte array byte[] testdata = ManagedHash.ComputeHmac(testKey, testData, alg); - Assert.IsTrue(testdata.Length == Math.Abs(hashSize)); + Assert.IsTrue(testdata.Length == ManagedHash.GetHashSize(alg)); //With input string testdata = ManagedHash.ComputeHmac(testKey, "test", alg); - Assert.IsTrue(testdata.Length == Math.Abs(hashSize)); + Assert.IsTrue(testdata.Length == ManagedHash.GetHashSize(alg)); //Compute hash as string string testEnc = ManagedHash.ComputeHmac(testKey, testdata, alg, HashEncodingMode.Hexadecimal); - Assert.IsTrue(testEnc.Length == Math.Abs(hashSize) * 2); + Assert.IsTrue(testEnc.Length == ManagedHash.GetHashSize(alg) * 2); //With input string testEnc = ManagedHash.ComputeHmac(testKey, "test", alg, HashEncodingMode.Hexadecimal); - Assert.IsTrue(testEnc.Length == Math.Abs(hashSize) * 2); + Assert.IsTrue(testEnc.Length == ManagedHash.GetHashSize(alg) * 2); } } diff --git a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs index e6b9f24..6c5ebcc 100644 --- a/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs +++ b/lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Essentials @@ -29,6 +29,15 @@ using VNLib.Hashing; using VNLib.Utils; using VNLib.Utils.Memory; +/* + * 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.Plugins.Essentials.Accounts { @@ -59,7 +68,8 @@ namespace VNLib.Plugins.Essentials.Accounts /// <param name="secret">The password secret provider</param> /// <param name="setup">The configuration setup arguments</param> /// <returns>The instance of the library to use</returns> - public static PasswordHashing Create(IArgon2Library library, ISecretProvider secret, in Argon2ConfigParams setup) => new (library, secret, setup); + public static PasswordHashing Create(IArgon2Library library, ISecretProvider secret, in Argon2ConfigParams setup) + => new (library, secret, in setup); /// <summary> /// Creates a new <see cref="PasswordHashing"/> instance using the default @@ -69,7 +79,8 @@ namespace VNLib.Plugins.Essentials.Accounts /// <param name="setup">The configuration setup arguments</param> /// <returns>The instance of the library to use</returns> /// <exception cref="DllNotFoundException"></exception> - public static PasswordHashing Create(ISecretProvider secret, in Argon2ConfigParams setup) => Create(VnArgon2.GetOrLoadSharedLib(), secret, in setup); + public static PasswordHashing Create(ISecretProvider secret, in Argon2ConfigParams setup) + => Create(VnArgon2.GetOrLoadSharedLib(), secret, in setup); private Argon2CostParams GetCostParams() { @@ -93,15 +104,18 @@ namespace VNLib.Plugins.Essentials.Accounts if(_secret.BufferSize < STACK_MAX_BUFF_SIZE) { - //Alloc stack buffer + /* + * Also always alloc fixed buffer size again to help + * be less obvious during process allocations + */ Span<byte> secretBuffer = stackalloc byte[STACK_MAX_BUFF_SIZE]; return VerifyInternal(passHash, password, secretBuffer); } else { - //Alloc heap buffer - using UnsafeMemoryHandle<byte> secretBuffer = MemoryUtil.UnsafeAlloc(_secret.BufferSize, true); + + using UnsafeMemoryHandle<byte> secretBuffer = AllocSecretBuffer(); return VerifyInternal(passHash, password, secretBuffer.Span); } @@ -111,10 +125,10 @@ namespace VNLib.Plugins.Essentials.Accounts { try { - //Get the secret from the callback - ERRNO count = _secret.GetSecret(secretBuffer); - //Verify - return _argon2.Verify2id(password, passHash, secretBuffer[..(int)count]); + + ERRNO secretSize = _secret.GetSecret(secretBuffer); + + return _argon2.Verify2id(password, passHash, secretBuffer[..(int)secretSize]); } finally { @@ -135,11 +149,9 @@ namespace VNLib.Plugins.Essentials.Accounts public bool Verify(ReadOnlySpan<byte> hash, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> password) { if (hash.Length < STACK_MAX_BUFF_SIZE) - { - //Alloc stack buffer + { Span<byte> hashBuf = stackalloc byte[hash.Length]; - - //Hash the password with the current config + Hash(password, salt, hashBuf); //Compare the hashed password to the specified hash and return results @@ -148,8 +160,7 @@ namespace VNLib.Plugins.Essentials.Accounts else { using UnsafeMemoryHandle<byte> hashBuf = MemoryUtil.UnsafeAlloc(hash.Length, true); - - //Hash the password with the current config + Hash(password, salt, hashBuf.Span); //Compare the hashed password to the specified hash and return results @@ -165,7 +176,7 @@ namespace VNLib.Plugins.Essentials.Accounts Argon2CostParams costParams = GetCostParams(); //Alloc shared buffer for the salt and secret buffer - using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc(_config.SaltLen + _secret.BufferSize, true); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAllocNearestPage(_config.SaltLen + _secret.BufferSize, true); //Split buffers Span<byte> saltBuf = buffer.Span[.._config.SaltLen]; @@ -175,12 +186,16 @@ namespace VNLib.Plugins.Essentials.Accounts RandomHash.GetRandomBytes(saltBuf); try - { - //recover the secret + { ERRNO count = _secret.GetSecret(secretBuf); - - //Hashes a password, with the current parameters - return (PrivateString)_argon2.Hash2id(password, saltBuf, secretBuf[..(int)count], in costParams, _config.HashLen); + + return (PrivateString)_argon2.Hash2id( + password: password, + salt: saltBuf, + secret: secretBuf[..(int)count], + costParams: in costParams, + hashLen: _config.HashLen + ); } finally { @@ -201,7 +216,10 @@ namespace VNLib.Plugins.Essentials.Accounts Span<byte> saltBuf = buffer.Span[.._config.SaltLen]; Span<byte> secretBuf = buffer.Span[_config.SaltLen..]; - //Fill the buffer with random bytes + /* + * Salt is just crypographically secure random + * data. + */ RandomHash.GetRandomBytes(saltBuf); try @@ -210,7 +228,13 @@ namespace VNLib.Plugins.Essentials.Accounts ERRNO count = _secret.GetSecret(secretBuf); //Hashes a password, with the current parameters - return (PrivateString)_argon2.Hash2id(password, saltBuf, secretBuf[..(int)count], in costParams, _config.HashLen); + return (PrivateString)_argon2.Hash2id( + password: password, + salt: saltBuf, + secret: secretBuf[..(int)count], + costParams: in costParams, + hashLen: _config.HashLen + ); } finally { @@ -229,20 +253,28 @@ namespace VNLib.Plugins.Essentials.Accounts public void Hash(ReadOnlySpan<byte> password, ReadOnlySpan<byte> salt, Span<byte> hashOutput) { Argon2CostParams costParams = GetCostParams(); + + using UnsafeMemoryHandle<byte> secretBuffer = AllocSecretBuffer(); - //alloc secret buffer - using UnsafeMemoryHandle<byte> secretBuffer = MemoryUtil.UnsafeAllocNearestPage(_secret.BufferSize, true); try { - //Get the secret from the callback - ERRNO count = _secret.GetSecret(secretBuffer.Span); - //Hashes a password, with the current parameters - _argon2.Hash2id(password, salt, secretBuffer.Span[..(int)count], hashOutput, in costParams); + ERRNO secretSize = _secret.GetSecret(secretBuffer.Span); + + _argon2.Hash2id( + password: password, + salt: salt, + secret: secretBuffer.AsSpan(0, secretSize), + rawHashOutput: hashOutput, + costParams: in costParams + ); } finally { //Erase secret buffer - MemoryUtil.InitializeBlock(ref secretBuffer.GetReference(), secretBuffer.IntLength); + MemoryUtil.InitializeBlock( + ref secretBuffer.GetReference(), + secretBuffer.IntLength + ); } } @@ -290,6 +322,12 @@ namespace VNLib.Plugins.Essentials.Accounts } } + /* + * Always alloc page aligned to help keep block allocations + * a little less obvious. + */ + private UnsafeMemoryHandle<byte> AllocSecretBuffer() => MemoryUtil.UnsafeAllocNearestPage(_secret.BufferSize, true); + private readonly ref struct HashBufferSegments { public readonly Span<byte> SaltBuffer; diff --git a/lib/Utils/src/Memory/IUnmangedHeap.cs b/lib/Utils/src/Memory/IUnmangedHeap.cs index fdc0f9b..e6f82e0 100644 --- a/lib/Utils/src/Memory/IUnmangedHeap.cs +++ b/lib/Utils/src/Memory/IUnmangedHeap.cs @@ -1,5 +1,5 @@ /* -* Copyright (c) 2023 Vaughn Nugent +* Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Utils @@ -42,7 +42,10 @@ namespace VNLib.Utils.Memory /// <param name="size">The size (in bytes) of the element</param> /// <param name="elements">The number of elements to allocate</param> /// <param name="zero">An optional parameter to zero the block of memory</param> - /// <returns></returns> + /// <returns>A memory address to a valid block on the heap</returns> + /// <remarks> + /// If the heap is unable to allocate the requested memory, an OutOfMemoryException will be thrown + /// </remarks> /// <exception cref="OutOfMemoryException"></exception> IntPtr Alloc(nuint elements, nuint size, bool zero); diff --git a/lib/Utils/src/Memory/UnmanagedHeapBase.cs b/lib/Utils/src/Memory/UnmanagedHeapBase.cs index 599d8d9..7f42761 100644 --- a/lib/Utils/src/Memory/UnmanagedHeapBase.cs +++ b/lib/Utils/src/Memory/UnmanagedHeapBase.cs @@ -67,7 +67,7 @@ namespace VNLib.Utils.Memory DangerousAddRef(ref handleCountIncremented); //Failed to increment ref count, class has been disposed - ObjectDisposedException.ThrowIf(handleCountIncremented == false, "The handle has been released"); + ObjectDisposedException.ThrowIf(handleCountIncremented == false, this); try { |