/* * Copyright (c) 2023 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Extensions.Loading * File: ManagedPasswordHashing.cs * * ManagedPasswordHashing.cs is part of VNLib.Plugins.Extensions.Loading which * is part of the larger VNLib collection of libraries and utilities. * * VNLib.Plugins.Extensions.Loading is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * VNLib.Plugins.Extensions.Loading 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ using System; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using System.Collections.Generic; using VNLib.Utils; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; using VNLib.Plugins.Essentials.Accounts; namespace VNLib.Plugins.Extensions.Loading { /// /// A plugin configurable managed implementation. Users may load custom /// assemblies backing instances of this class or configure the implementation /// [ConfigurationName(LoadingExtensions.PASSWORD_HASHING_KEY, Required = false)] public sealed class ManagedPasswordHashing : IPasswordHashingProvider { public ManagedPasswordHashing(PluginBase plugin, IConfigScope config) { //Check for custom hashing assembly if (config.TryGetValue(LoadingExtensions.CUSTOM_PASSWORD_ASM_KEY, out JsonElement el)) { string customAsm = el.GetString() ?? throw new KeyNotFoundException("You must specify a string file path for your custom password hashing assembly"); //Load the custom assembly AssemblyLoader prov = plugin.LoadAssembly(customAsm); //Configure async if (prov.Resource is IAsyncConfigurable ac) { //Configure async _ = plugin.ConfigureServiceAsync(ac); } //Store Passwords = new CustomPasswordHashingAsm(prov); } else { Passwords = plugin.GetOrCreateSingleton().Passwords; } } public ManagedPasswordHashing(PluginBase plugin) { //Only configure a default password impl Passwords = plugin.GetOrCreateSingleton().Passwords; } /// /// The underlying /// public IPasswordHashingProvider Passwords { get; } /// public bool Verify(ReadOnlySpan passHash, ReadOnlySpan password) => Passwords.Verify(passHash, password); /// public bool Verify(ReadOnlySpan passHash, ReadOnlySpan password) => Passwords.Verify(passHash, password); /// public PrivateString Hash(ReadOnlySpan password) => Passwords.Hash(password); /// public PrivateString Hash(ReadOnlySpan password) => Passwords.Hash(password); /// public ERRNO Hash(ReadOnlySpan password, Span hashOutput) => Passwords.Hash(password, hashOutput); sealed class CustomPasswordHashingAsm : IPasswordHashingProvider { private readonly AssemblyLoader _loader; public CustomPasswordHashingAsm(AssemblyLoader loader) { _loader = loader; } /* * Password hashing isnt a super high performance system * so adding method overhead shouldnt be a large issue for the * asm wrapper providing unload protection */ public PrivateString Hash(ReadOnlySpan password) => _loader.Resource.Hash(password); public PrivateString Hash(ReadOnlySpan password) => _loader.Resource.Hash(password); public ERRNO Hash(ReadOnlySpan password, Span hashOutput) => _loader.Resource.Hash(password, hashOutput); public bool Verify(ReadOnlySpan passHash, ReadOnlySpan password) => _loader.Resource.Verify(passHash, password); public bool Verify(ReadOnlySpan passHash, ReadOnlySpan password) => _loader.Resource.Verify(passHash, password); } private sealed class SecretProvider : VnDisposeable, ISecretProvider, IAsyncConfigurable { private byte[]? _pepper; private Exception? _error; public SecretProvider(PluginBase plugin, IConfigScope config) { if (config.TryGetValue("args", out JsonElement el)) { //Convert to dict IReadOnlyDictionary hashingArgs = el.EnumerateObject().ToDictionary(static k => k.Name, static v => v.Value); //Get hashing arguments uint saltLen = hashingArgs["salt_len"].GetUInt32(); uint hashLen = hashingArgs["hash_len"].GetUInt32(); uint timeCost = hashingArgs["time_cost"].GetUInt32(); uint memoryCost = hashingArgs["memory_cost"].GetUInt32(); uint parallelism = hashingArgs["parallelism"].GetUInt32(); //Load passwords Passwords = new(this, (int)saltLen, timeCost, memoryCost, parallelism, hashLen); } else { Passwords = new(this); } } public SecretProvider(PluginBase plugin) { Passwords = new(this); } public PasswordHashing Passwords { get; } /// public int BufferSize { get { Check(); return _pepper!.Length; } } public ERRNO GetSecret(Span buffer) { Check(); //Coppy pepper to buffer _pepper.CopyTo(buffer); //Return pepper length return _pepper!.Length; } protected override void Check() { base.Check(); if (_error != null) { throw _error; } } protected override void Free() { //Clear the pepper if set MemoryUtil.InitializeBlock(_pepper.AsSpan()); } public async Task ConfigureServiceAsync(PluginBase plugin) { try { //Get the pepper from secret storage _pepper = await plugin.TryGetSecretAsync(LoadingExtensions.PASSWORD_HASHING_KEY).ToBase64Bytes(); } catch (Exception ex) { //Store exception for re-propagation _error = ex; //Propagate exception to system throw; } } } } }