aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Hashing.Portable/src/Argon2/Argon2PasswordEntry.cs45
-rw-r--r--lib/Hashing.Portable/src/Argon2/VnArgon2.cs250
-rw-r--r--lib/Hashing.Portable/tests/ManagedHashTests.cs27
-rw-r--r--lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs100
-rw-r--r--lib/Utils/src/Memory/IUnmangedHeap.cs7
-rw-r--r--lib/Utils/src/Memory/UnmanagedHeapBase.cs2
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
{