diff options
Diffstat (limited to 'lib/Plugins.Essentials/src')
-rw-r--r-- | lib/Plugins.Essentials/src/Accounts/PasswordHashing.cs | 100 |
1 files changed, 69 insertions, 31 deletions
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; |