aboutsummaryrefslogtreecommitdiff
path: root/back-end/libs
diff options
context:
space:
mode:
Diffstat (limited to 'back-end/libs')
-rw-r--r--back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs175
-rw-r--r--back-end/libs/NVault.Crypto.Secp256k1/src/IRandomSource.cs32
-rw-r--r--back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs209
-rw-r--r--back-end/libs/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj27
-rw-r--r--back-end/libs/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs79
-rw-r--r--back-end/libs/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs101
-rw-r--r--back-end/libs/NVault.VaultExtensions/src/IClientAccessScope.cs48
-rw-r--r--back-end/libs/NVault.VaultExtensions/src/IKvVaultStore.cs52
-rw-r--r--back-end/libs/NVault.VaultExtensions/src/IVaultClientScope.cs33
-rw-r--r--back-end/libs/NVault.VaultExtensions/src/IVaultKvClientScope.cs29
-rw-r--r--back-end/libs/NVault.VaultExtensions/src/KvVaultStorage.cs66
-rw-r--r--back-end/libs/NVault.VaultExtensions/src/NVault.VaultExtensions.csproj27
-rw-r--r--back-end/libs/NVault.VaultExtensions/src/VaultClientExtensions.cs156
-rw-r--r--back-end/libs/NVault.VaultExtensions/src/VaultUserScope.cs25
14 files changed, 1059 insertions, 0 deletions
diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs b/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs
new file mode 100644
index 0000000..b0e8695
--- /dev/null
+++ b/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs
@@ -0,0 +1,175 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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.Security.Cryptography;
+
+using VNLib.Hashing;
+using VNLib.Utils;
+using VNLib.Utils.Memory;
+using VNLib.Utils.Extensions;
+
+using static NVault.Crypto.Secp256k1.LibSecp256k1;
+
+
+namespace NVault.Crypto.Secp256k1
+{
+ public static unsafe class ContextExtensions
+ {
+ /// <summary>
+ /// Creates a new <see cref="Secp256k1Context"/> from the current managed library
+ /// </summary>
+ /// <param name="Lib"></param>
+ /// <returns>The new <see cref="Secp256k1Context"/> object from the library</returns>
+ /// <exception cref="CryptographicException"></exception>
+ public static Secp256k1Context CreateContext(this LibSecp256k1 Lib)
+ {
+ //Protect for released lib
+ Lib.SafeLibHandle.ThrowIfClosed();
+
+ //Create new context
+ IntPtr context = Lib._create(1);
+
+ if (context == IntPtr.Zero)
+ {
+ throw new CryptographicException("Failed to create the new Secp256k1 context");
+ }
+
+ return new Secp256k1Context(Lib, context);
+ }
+
+ /// <summary>
+ /// Signs a 32byte message digest with the specified secret key on the current context and writes the signature to the specified buffer
+ /// </summary>
+ /// <param name="context"></param>
+ /// <param name="secretKey">The 32byte secret key used to sign messages from the user</param>
+ /// <param name="digest">The 32byte message digest to compute the signature of</param>
+ /// <param name="signature">The buffer to write the signature output to, must be at-least 64 bytes</param>
+ /// <returns>The number of bytes written to the signature buffer, or less than 1 if the operation failed</returns>
+ public static ERRNO SchnorSignDigest(this in Secp256k1Context context, ReadOnlySpan<byte> secretKey, ReadOnlySpan<byte> digest, Span<byte> signature)
+ {
+ //Check the signature buffer size
+ if (signature.Length < SignatureSize)
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //Message digest must be exactly 32 bytes long
+ if (digest.Length != (int)HashAlg.SHA256)
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //Stack allocated keypair
+ KeyPair keyPair = new();
+
+ //Randomize the context and create the keypair
+ if (!context.CreateKeyPair(&keyPair, secretKey))
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //Create the random nonce
+ byte* random = stackalloc byte[RandomBufferSize];
+
+ //Fill the buffer with random bytes
+ context.Lib.GetRandomBytes(new Span<byte>(random, RandomBufferSize));
+
+ try
+ {
+ fixed (byte* sigPtr = signature, digestPtr = digest)
+ {
+ //Sign the message hash and write the output to the signature buffer
+ if (context.Lib._signHash(context.Context, sigPtr, digestPtr, &keyPair, random) != 1)
+ {
+ return ERRNO.E_FAIL;
+ }
+ }
+ }
+ finally
+ {
+ //Erase entropy
+ MemoryUtil.InitializeBlock(random, RandomBufferSize);
+
+ //Clear the keypair, contains the secret key, even if its stack allocated
+ MemoryUtil.ZeroStruct(&keyPair);
+ }
+
+ //Signature size is always 64 bytes
+ return SignatureSize;
+ }
+
+ /// <summary>
+ /// Generates an x-only Schnor encoded public key from the specified secret key on the
+ /// current context and writes it to the specified buffer.
+ /// </summary>
+ /// <param name="context"></param>
+ /// <param name="secretKey">The 32byte secret key used to derrive the public key from</param>
+ /// <param name="pubKeyBuffer">The buffer to write the x-only Schnor encoded public key</param>
+ /// <returns>The number of bytes written to the output buffer, or 0 if the operation failed</returns>
+ /// <exception cref="CryptographicException"></exception>
+ public static ERRNO GeneratePubKeyFromSecret(this in Secp256k1Context context, ReadOnlySpan<byte> secretKey, Span<byte> pubKeyBuffer)
+ {
+ if (secretKey.Length != SecretKeySize)
+ {
+ throw new CryptographicException($"Your secret key must be exactly {SecretKeySize} bytes long");
+ }
+
+ if (pubKeyBuffer.Length < XOnlyPublicKeySize)
+ {
+ throw new CryptographicException($"Your public key buffer must be at least {XOnlyPublicKeySize} bytes long");
+ }
+
+ //Protect for released lib
+ context.Lib.SafeLibHandle.ThrowIfClosed();
+
+ //Stack allocated keypair and x-only public key
+ XOnlyPubKey xOnlyPubKey = new();
+ KeyPair keyPair = new();
+
+ try
+ {
+ //Init context and keypair
+ if (!context.CreateKeyPair(&keyPair, secretKey))
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ //X-only public key from the keypair
+ if (context.Lib._createXonly(context.Context, &xOnlyPubKey, 0, &keyPair) != 1)
+ {
+ return ERRNO.E_FAIL;
+ }
+
+ fixed (byte* pubBuffer = pubKeyBuffer)
+ {
+ //Serialize the public key to the buffer as an X-only public key without leading status byte
+ if (context.Lib._serializeXonly(context.Context, pubBuffer, &xOnlyPubKey) != 1)
+ {
+ return ERRNO.E_FAIL;
+ }
+ }
+ }
+ finally
+ {
+ //Clear the keypair, contains the secret key, even if its stack allocated
+ MemoryUtil.ZeroStruct(&keyPair);
+ }
+
+ //PubKey length is constant
+ return XOnlyPublicKeySize;
+ }
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/IRandomSource.cs b/back-end/libs/NVault.Crypto.Secp256k1/src/IRandomSource.cs
new file mode 100644
index 0000000..542fc9c
--- /dev/null
+++ b/back-end/libs/NVault.Crypto.Secp256k1/src/IRandomSource.cs
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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;
+
+namespace NVault.Crypto.Secp256k1
+{
+ /// <summary>
+ /// Represents a generator for random data, that fills abinary buffer with random bytes
+ /// on demand.
+ /// </summary>
+ public interface IRandomSource
+ {
+ /// <summary>
+ /// Fills the given buffer with random bytes
+ /// </summary>
+ /// <param name="buffer">Binary buffer to fill with random data</param>
+ void GetRandomBytes(Span<byte> buffer);
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs b/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs
new file mode 100644
index 0000000..2eea450
--- /dev/null
+++ b/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs
@@ -0,0 +1,209 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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.Runtime.InteropServices;
+
+using VNLib.Hashing;
+using VNLib.Utils;
+using VNLib.Utils.Native;
+using VNLib.Utils.Extensions;
+
+namespace NVault.Crypto.Secp256k1
+{
+
+ public unsafe class LibSecp256k1 : VnDisposeable
+ {
+ public const int SecretKeySize = 32;
+ public const int XOnlyPublicKeySize = 32;
+ public const int SignatureSize = 64;
+ public const int KeyPairSize = 96;
+ public const int RandomBufferSize = 32;
+
+ /*
+ * Unsafe structures that represent the native keypair and x-only public key
+ * structures. They hold character arrays
+ */
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct KeyPair
+ {
+ public fixed byte data[Length];
+ public const int Length = KeyPairSize;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct XOnlyPubKey
+ {
+ public fixed byte data[Length];
+ public const int Length = 64;
+ }
+
+ //Native methods
+ [SafeMethodName("secp256k1_context_create")]
+ internal delegate IntPtr CreateContext(int flags);
+
+ [SafeMethodName("secp256k1_context_destroy")]
+ internal delegate void DestroyContext(IntPtr context);
+
+ [SafeMethodName("secp256k1_context_randomize")]
+ internal delegate int RandomizeContext(IntPtr context, byte* seed32);
+
+ [SafeMethodName("secp256k1_keypair_create")]
+ internal delegate int KeypairCreate(IntPtr context, KeyPair* keyPair, byte* secretKey);
+
+ [SafeMethodName("secp256k1_keypair_xonly_pub")]
+ internal delegate int KeypairXOnlyPub(IntPtr ctx, XOnlyPubKey* pubkey, int pk_parity, KeyPair* keypair);
+
+ [SafeMethodName("secp256k1_xonly_pubkey_serialize")]
+ internal delegate int XOnlyPubkeySerialize(IntPtr ctx, byte* output32, XOnlyPubKey* pubkey);
+
+ [SafeMethodName("secp256k1_schnorrsig_sign32")]
+ internal delegate int SignHash(IntPtr ctx, byte* sig64, byte* msg32, KeyPair* keypair, byte* aux_rand32);
+
+ /// <summary>
+ /// Loads the Secp256k1 library from the specified path and creates a wrapper class (loads methods from the library)
+ /// </summary>
+ /// <param name="dllPath">The realtive or absolute path to the shared library</param>
+ /// <param name="search">The DLL probing path pattern</param>
+ /// <returns>The <see cref="LibSecp256k1"/> library wrapper class</returns>
+ /// <exception cref="DllNotFoundException"></exception>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="MissingMemberException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public static LibSecp256k1 LoadLibrary(string dllPath, DllImportSearchPath search, IRandomSource? random)
+ {
+ _ = dllPath?? throw new ArgumentNullException(nameof(dllPath));
+
+ //try to load the library
+ SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(dllPath, search);
+
+ //try to create the wrapper class, if it fails, dispose the library
+ try
+ {
+ //setup fallback random source if null
+ random ??= new FallbackRandom();
+
+ //Create the lib
+ return new LibSecp256k1(lib, random);
+ }
+ catch
+ {
+ //Dispose the library if the creation failed
+ lib.Dispose();
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// The underlying library handle
+ /// </summary>
+ public SafeLibraryHandle SafeLibHandle { get; }
+
+ internal readonly KeypairCreate _createKeyPair;
+ internal readonly CreateContext _create;
+ internal readonly RandomizeContext _randomize;
+ internal readonly DestroyContext _destroy;
+ internal readonly KeypairXOnlyPub _createXonly;
+ internal readonly XOnlyPubkeySerialize _serializeXonly;
+ internal readonly SignHash _signHash;
+ private readonly IRandomSource _randomSource;
+
+ /// <summary>
+ /// Creates a new instance of the <see cref="LibSecp256k1"/> class from the specified library handle
+ /// </summary>
+ /// <param name="handle">The library handle that referrences the secp256k1 platform specific library</param>
+ /// <remarks>
+ /// This method attempts to capture all the native methods from the library, which may throw if the library is not valid.
+ /// </remarks>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="MissingMemberException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public LibSecp256k1(SafeLibraryHandle handle, IRandomSource randomSource)
+ {
+ //Store library handle
+ SafeLibHandle = handle ?? throw new ArgumentNullException(nameof(handle));
+
+ //Get all method handles and store them
+ _create = handle.DangerousGetMethod<CreateContext>();
+ _createKeyPair = handle.DangerousGetMethod<KeypairCreate>();
+ _randomize = handle.DangerousGetMethod<RandomizeContext>();
+ _destroy = handle.DangerousGetMethod<DestroyContext>();
+ _createXonly = handle.DangerousGetMethod<KeypairXOnlyPub>();
+ _serializeXonly = handle.DangerousGetMethod<XOnlyPubkeySerialize>();
+ _signHash = handle.DangerousGetMethod<SignHash>();
+
+ //Store random source
+ _randomSource = randomSource;
+ }
+
+ /// <summary>
+ /// Creates a new instance of the <see cref="LibSecp256k1"/> class from the specified library handle
+ /// with a fallback random source
+ /// </summary>
+ /// <param name="handle">The library handle</param>
+ /// <exception cref="ArgumentNullException"></exception>
+ /// <exception cref="MissingMemberException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public LibSecp256k1(SafeLibraryHandle handle):this(handle, new FallbackRandom())
+ {}
+
+ /// <summary>
+ /// Generates a new secret key and writes it to the specified buffer. The buffer size must be exactly <see cref="SecretKeySize"/> bytes long
+ /// </summary>
+ /// <param name="buffer">The secret key buffer</param>
+ /// <exception cref="ArgumentException"></exception>
+ public void CreateSecretKey(Span<byte> buffer)
+ {
+ //Protect for released lib
+ SafeLibHandle.ThrowIfClosed();
+
+ if(buffer.Length != SecretKeySize)
+ {
+ throw new ArgumentException($"Buffer must be exactly {SecretKeySize} bytes long", nameof(buffer));
+ }
+
+ //Fill the buffer with random bytes
+ _randomSource.GetRandomBytes(buffer);
+ }
+
+ /// <summary>
+ /// Fills the given buffer with random bytes from
+ /// the internal random source
+ /// </summary>
+ /// <param name="buffer">The buffer to fill with random data</param>
+ public void GetRandomBytes(Span<byte> buffer)
+ {
+ //Protect for released lib
+ SafeLibHandle.ThrowIfClosed();
+
+ _randomSource.GetRandomBytes(buffer);
+ }
+
+ protected override void Free()
+ {
+ //Free native library
+ SafeLibHandle.Dispose();
+ }
+
+ private record class FallbackRandom : IRandomSource
+ {
+ public void GetRandomBytes(Span<byte> buffer)
+ {
+ //Use the random generator from the crypto lib
+ RandomHash.GetRandomBytes(buffer);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj b/back-end/libs/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj
new file mode 100644
index 0000000..fd64768
--- /dev/null
+++ b/back-end/libs/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj
@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <RootNamespace>NVault.Crypto.Secp256k1</RootNamespace>
+ <AssemblyName>NVault.Crypto.Secp256k1</AssemblyName>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>NVault.Crypto.Secp256k1</Product>
+ <Description>Provides a managed library for the Bitcoin Core secp256k1 library, along with other helper types for NVault</Description>
+ <Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/NVault</PackageProjectUrl>
+ <RepositoryUrl>https://github.com/VnUgE/NVault/tree/master/</RepositoryUrl>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="VNLib.Hashing.Portable" Version="0.1.0-ci0070" />
+ <PackageReference Include="VNLib.Utils" Version="0.1.0-ci0070" />
+ </ItemGroup>
+
+</Project>
diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs b/back-end/libs/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs
new file mode 100644
index 0000000..8aac0b8
--- /dev/null
+++ b/back-end/libs/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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 VNLib.Utils.Extensions;
+using VNLib.Utils.Memory;
+
+using static NVault.Crypto.Secp256k1.LibSecp256k1;
+
+namespace NVault.Crypto.Secp256k1
+{
+ /// <summary>
+ /// Represents a Secp256k1 context, it is used to randomize the instance, create key pairs,
+ /// and frees the context when disposed
+ /// </summary>
+ /// <param name="Lib">The <see cref="LibSecp256k1"/> library instance</param>
+ /// <param name="Context">A pointer to the initialized context instance</param>
+ public readonly record struct Secp256k1Context(LibSecp256k1 Lib, IntPtr Context) : IDisposable
+ {
+ /// <summary>
+ /// Randomizes the context with random data using the library's random source
+ /// </summary>
+ /// <returns>True if the context was successfully randomized, false otherwise</returns>
+ public unsafe readonly bool Randomize()
+ {
+ Lib.SafeLibHandle.ThrowIfClosed();
+
+ //Randomze the context
+ byte* entropy = stackalloc byte[RandomBufferSize];
+
+ //Get random bytes
+ Lib.GetRandomBytes(new Span<byte>(entropy, RandomBufferSize));
+
+ //call native randomize method
+ bool result = Lib._randomize(Context, entropy) == 1;
+
+ //Zero the randomness buffer before returning to avoid leaking random data
+ MemoryUtil.InitializeBlock(entropy, RandomBufferSize);
+
+ return result;
+ }
+
+ internal unsafe readonly bool CreateKeyPair(KeyPair* keyPair, ReadOnlySpan<byte> secretKey)
+ {
+ Lib.SafeLibHandle.ThrowIfClosed();
+
+ fixed (byte* sk = secretKey)
+ {
+ //Create the keypair from the secret key
+ return Lib._createKeyPair(Context, keyPair, sk) == 1;
+ }
+ }
+
+ /// <summary>
+ /// Releases the context instance and frees the memory
+ /// </summary>
+ public readonly void Dispose()
+ {
+ if (Context != IntPtr.Zero)
+ {
+ //Free the context
+ Lib._destroy(Context);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs b/back-end/libs/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs
new file mode 100644
index 0000000..d4d9a06
--- /dev/null
+++ b/back-end/libs/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs
@@ -0,0 +1,101 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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.Runtime.InteropServices;
+
+using VNLib.Utils;
+using VNLib.Utils.Native;
+using VNLib.Utils.Extensions;
+
+namespace NVault.Crypto.Secp256k1
+{
+
+ /// <summary>
+ /// A wrapper class for an unmanaged random source that conforms to the <see cref="IRandomSource"/> interface
+ /// </summary>
+ public class UnmanagedRandomSource : VnDisposeable, IRandomSource
+ {
+ public const string METHOD_NAME = "getRandomBytes";
+
+ unsafe delegate void UnmanagedRandomSourceDelegate(byte* buffer, int size);
+
+
+ private readonly bool OwnsHandle;
+ private readonly SafeLibraryHandle _library;
+ private readonly UnmanagedRandomSourceDelegate _getRandomBytes;
+
+ /// <summary>
+ /// Loads the unmanaged random source from the given library
+ /// and attempts to get the random bytes method <see cref="METHOD_NAME"/>
+ /// </summary>
+ /// <param name="path"></param>
+ /// <param name="search"></param>
+ /// <returns>The wrapped library that conforms to the <see cref="IRandomSource"/></returns>
+ public static UnmanagedRandomSource LoadLibrary(string path, DllImportSearchPath search)
+ {
+ //Try to load the library
+ SafeLibraryHandle lib = SafeLibraryHandle.LoadLibrary(path, search);
+ try
+ {
+ return new UnmanagedRandomSource(lib, true);
+ }
+ catch
+ {
+ //release lib
+ lib.Dispose();
+ throw;
+ }
+ }
+
+ /// <summary>
+ /// Creates the unmanaged random source from the given library
+ /// </summary>
+ /// <param name="lib">The library handle to wrap</param>
+ /// <exception cref="ObjectDisposedException"></exception>
+ /// <exception cref="EntryPointNotFoundException"></exception>
+ public UnmanagedRandomSource(SafeLibraryHandle lib, bool ownsHandle)
+ {
+ lib.ThrowIfClosed();
+
+ _library = lib;
+
+ //get the method delegate
+ _getRandomBytes = lib.DangerousGetMethod<UnmanagedRandomSourceDelegate>(METHOD_NAME);
+
+ OwnsHandle = ownsHandle;
+ }
+
+ public unsafe void GetRandomBytes(Span<byte> buffer)
+ {
+ _library.ThrowIfClosed();
+
+ //Fix buffer and call unmanaged method
+ fixed(byte* ptr = buffer)
+ {
+ _getRandomBytes(ptr, buffer.Length);
+ }
+ }
+
+ ///<inheritdoc/>
+ protected override void Free()
+ {
+ if (OwnsHandle)
+ {
+ _library.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.VaultExtensions/src/IClientAccessScope.cs b/back-end/libs/NVault.VaultExtensions/src/IClientAccessScope.cs
new file mode 100644
index 0000000..7a83fd7
--- /dev/null
+++ b/back-end/libs/NVault.VaultExtensions/src/IClientAccessScope.cs
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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.Collections.Generic;
+
+namespace NVault.VaultExtensions
+{
+ /// <summary>
+ /// Represents a user auth token access scope
+ /// configuration.
+ /// </summary>
+ public interface IClientAccessScope
+ {
+ /// <summary>
+ /// The list of policies for new token generation
+ /// </summary>
+ IList<string> Policies { get; }
+
+ /// <summary>
+ /// Allows the user to renew the access token
+ /// </summary>
+ bool Renewable { get; }
+
+ /// <summary>
+ /// The token
+ /// </summary>
+ string TokenTtl { get; }
+
+ /// <summary>
+ /// The explicit number of token uses allowed by the genreated token,
+ /// 0 for unlimited uses
+ /// </summary>
+ int NumberOfUses { get; }
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.VaultExtensions/src/IKvVaultStore.cs b/back-end/libs/NVault.VaultExtensions/src/IKvVaultStore.cs
new file mode 100644
index 0000000..261bd7c
--- /dev/null
+++ b/back-end/libs/NVault.VaultExtensions/src/IKvVaultStore.cs
@@ -0,0 +1,52 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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.Threading.Tasks;
+
+using VNLib.Utils.Memory;
+
+namespace NVault.VaultExtensions
+{
+ /// <summary>
+ /// Represents a vault key-value store that can be used to store secrets
+ /// </summary>
+ public interface IKvVaultStore
+ {
+ /// <summary>
+ /// Deletes a secret from the vault
+ /// </summary>
+ /// <param name="user">The user scope of the secret</param>
+ /// <param name="path">The path to the secret</param>
+ /// <returns>A task that returns when the operation has completed</returns>
+ Task DeleteSecretAsync(VaultUserScope user, string path);
+
+ /// <summary>
+ /// Sets a secret in the vault at the specified path and user scope
+ /// </summary>
+ /// <param name="user">The user scope to store the value at</param>
+ /// <param name="path">The path to the secret</param>
+ /// <param name="secret">The secret value to set</param>
+ /// <returns>A task that resolves when the secret has been updated</returns>
+ Task SetSecretAsync(VaultUserScope user, string path, PrivateString secret);
+
+ /// <summary>
+ /// Gets a secret from the vault at the specified path and user scope
+ /// </summary>
+ /// <param name="user">The user scope to get the value from</param>
+ /// <param name="path">The secret path</param>
+ /// <returns>A task that resolves the secret if found, null otherwise</returns>
+ Task<PrivateString?> GetSecretAsync(VaultUserScope user, string path);
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.VaultExtensions/src/IVaultClientScope.cs b/back-end/libs/NVault.VaultExtensions/src/IVaultClientScope.cs
new file mode 100644
index 0000000..873a115
--- /dev/null
+++ b/back-end/libs/NVault.VaultExtensions/src/IVaultClientScope.cs
@@ -0,0 +1,33 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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/>.
+
+namespace NVault.VaultExtensions
+{
+ /// <summary>
+ /// Represents a vault client scope configuration
+ /// </summary>
+ public interface IVaultClientScope
+ {
+ /// <summary>
+ /// The mount point for the vault
+ /// </summary>
+ string? MountPoint { get; }
+
+ /// <summary>
+ /// The entry path for the vault
+ /// </summary>
+ string? EntryPath { get; }
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.VaultExtensions/src/IVaultKvClientScope.cs b/back-end/libs/NVault.VaultExtensions/src/IVaultKvClientScope.cs
new file mode 100644
index 0000000..951f5e2
--- /dev/null
+++ b/back-end/libs/NVault.VaultExtensions/src/IVaultKvClientScope.cs
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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/>.
+
+namespace NVault.VaultExtensions
+{
+ /// <summary>
+ /// A key-value specific scoped client
+ /// </summary>
+ public interface IVaultKvClientScope : IVaultClientScope
+ {
+ /// <summary>
+ /// The property to store the secret value in the
+ /// storage dictionary
+ /// </summary>
+ string StorageProperty { get; }
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.VaultExtensions/src/KvVaultStorage.cs b/back-end/libs/NVault.VaultExtensions/src/KvVaultStorage.cs
new file mode 100644
index 0000000..b679404
--- /dev/null
+++ b/back-end/libs/NVault.VaultExtensions/src/KvVaultStorage.cs
@@ -0,0 +1,66 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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.Threading.Tasks;
+
+using VaultSharp;
+
+using VNLib.Utils.Memory;
+
+namespace NVault.VaultExtensions
+{
+ /// <summary>
+ /// An abstract kv storage implementation that uses the vault client to store secrets
+ /// </summary>
+ public abstract class KvVaultStorage : IKvVaultStore
+ {
+ /// <summary>
+ /// The vault client
+ /// </summary>
+ protected abstract IVaultClient Client { get; }
+
+ /// <summary>
+ /// The storage scope
+ /// </summary>
+ protected abstract IVaultKvClientScope Scope { get; }
+
+ public virtual Task DeleteSecretAsync(VaultUserScope user, string path)
+ {
+ string tPath = TranslatePath(path);
+ return Client.DeleteSecretAsync(Scope, user, tPath);
+ }
+
+ public virtual Task SetSecretAsync(VaultUserScope user, string path, PrivateString secret)
+ {
+ string tPath = TranslatePath(path);
+ return Client.SetSecretAsync(Scope, user, tPath, secret);
+ }
+
+ public virtual Task<PrivateString?> GetSecretAsync(VaultUserScope user, string path)
+ {
+ string tPath = TranslatePath(path);
+ return Client.GetSecretAsync(Scope, user, tPath);
+ }
+
+ /// <summary>
+ /// Translates a realtive item path to a full path
+ /// within the scope of the storage. This may be used to
+ /// extend the scope of the operation
+ /// </summary>
+ /// <param name="path">The item path to scope</param>
+ /// <returns>The further scoped vault path for the item</returns>
+ public virtual string TranslatePath(string path) => path;
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.VaultExtensions/src/NVault.VaultExtensions.csproj b/back-end/libs/NVault.VaultExtensions/src/NVault.VaultExtensions.csproj
new file mode 100644
index 0000000..43f3e15
--- /dev/null
+++ b/back-end/libs/NVault.VaultExtensions/src/NVault.VaultExtensions.csproj
@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net6.0</TargetFramework>
+ <Nullable>enable</Nullable>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <RootNamespace>NVault.VaultExtensions</RootNamespace>
+ <AssemblyName>NVault.VaultExtensions</AssemblyName>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Authors>Vaughn Nugent</Authors>
+ <Company>Vaughn Nugent</Company>
+ <Product>NVault.VaultExtensions</Product>
+ <Description>A Hashicorp Vault unified extension library for NVault</Description>
+ <Copyright>Copyright © 2023 Vaughn Nugent</Copyright>
+ <PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/NVault</PackageProjectUrl>
+ <RepositoryUrl>https://github.com/VnUgE/NVault/tree/master/</RepositoryUrl>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="VaultSharp" Version="1.13.0.1" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Loading" Version="0.1.0-ci0033" />
+ </ItemGroup>
+
+</Project>
diff --git a/back-end/libs/NVault.VaultExtensions/src/VaultClientExtensions.cs b/back-end/libs/NVault.VaultExtensions/src/VaultClientExtensions.cs
new file mode 100644
index 0000000..5a7c637
--- /dev/null
+++ b/back-end/libs/NVault.VaultExtensions/src/VaultClientExtensions.cs
@@ -0,0 +1,156 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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.Threading;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+
+using VaultSharp;
+using VaultSharp.V1.Commons;
+
+using VNLib.Utils.Memory;
+using VNLib.Plugins.Essentials.Extensions;
+
+
+namespace NVault.VaultExtensions
+{
+
+ public static class VaultClientExtensions
+ {
+
+ private static string GetKeyPath(IVaultClientScope client, in VaultUserScope scope, string itemPath)
+ {
+ //Allow for null entry path
+ return client.EntryPath == null ? $"{scope.UserId}/{itemPath}" : $"{client.EntryPath}/{scope.UserId}/{itemPath}";
+ }
+
+
+ public static Task<PrivateString?> GetSecretAsync(this IVaultClient client, IVaultKvClientScope scope, VaultUserScope user, string path)
+ {
+ return GetSecretAsync(client, scope, user, path, scope.StorageProperty);
+ }
+
+ public static async Task<PrivateString?> GetSecretAsync(this IVaultClient client, IVaultClientScope scope, VaultUserScope user, string path, string property)
+ {
+ //Get the path complete path for the scope
+ string fullPath = GetKeyPath(scope, user, path);
+
+ //Get the secret from the vault
+ Secret<SecretData> result = await client.V1.Secrets.KeyValue.V2.ReadSecretAsync(fullPath, mountPoint:scope.MountPoint);
+
+ //Try to get the secret value from the store
+ string? value = result.Data.Data.GetValueOrDefault(property)?.ToString();
+
+ //Return the secret value as a private string
+ return value == null ? null : new PrivateString(value);
+ }
+
+ /// <summary>
+ /// Writes a secret to the vault that is scoped by the vault scope, and the user scope.
+ /// </summary>
+ /// <param name="client"></param>
+ /// <param name="scope">The client scope configuration</param>
+ /// <param name="user">The user scope to isolate the </param>
+ /// <param name="path">The item path within the current scope</param>
+ /// <param name="secret">The secret value to set at the desired property</param>
+ /// <returns>A task that resolves when the secret has been updated</returns>
+ public static async Task<CurrentSecretMetadata> SetSecretAsync(this IVaultClient client, IVaultKvClientScope scope, VaultUserScope user, string path, PrivateString secret)
+ {
+ Dictionary<string, string> secretDict = new()
+ {
+ //Dangerous cast, but we know the type
+ { scope.StorageProperty, (string)secret }
+ };
+
+ //Await the result so we be sure the secret is not destroyed
+ return await SetSecretAsync(client, scope, user, path, secretDict);
+ }
+
+ /// <summary>
+ /// Writes a secret to the vault that is scoped by the vault scope, and the user scope.
+ /// </summary>
+ /// <param name="client"></param>
+ /// <param name="scope">The client scope configuration</param>
+ /// <param name="user">The user scope to isolate the </param>
+ /// <param name="path">The item path within the current scope</param>
+ /// <param name="secret">The secret value to set at the desired property</param>
+ /// <returns>A task that resolves when the secret has been updated</returns>
+ public static async Task<CurrentSecretMetadata> SetSecretAsync(this IVaultClient client, IVaultClientScope scope, VaultUserScope user, string path, IDictionary<string, string> secret)
+ {
+ //Get the path complete path for the scope
+ string fullPath = GetKeyPath(scope, user, path);
+
+ //Get the secret from the vault
+ Secret<CurrentSecretMetadata> result = await client.V1.Secrets.KeyValue.V2.WriteSecretAsync(fullPath, secret, mountPoint:scope.MountPoint);
+
+ return result.Data;
+ }
+
+ /// <summary>
+ /// Deletes a secret from the vault that is scoped by the vault scope, and the user scope.
+ /// </summary>
+ /// <param name="client"></param>
+ /// <param name="scope">The client scope</param>
+ /// <param name="user">The vault user scope</param>
+ /// <param name="path">The path to the storage</param>
+ /// <returns>A task that resolves when the delete operation has completed</returns>
+ public static Task DeleteSecretAsync(this IVaultClient client, IVaultClientScope scope, VaultUserScope user, string path)
+ {
+ string fullApth = GetKeyPath(scope, user, path);
+ return client.V1.Secrets.KeyValue.V2.DeleteSecretAsync(fullApth, mountPoint:scope.MountPoint);
+ }
+
+ /// <summary>
+ /// Deletes a secret from the vault
+ /// </summary>
+ /// <param name="user">The user scope of the secret</param>
+ /// <param name="path">The path to the secret</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that returns when the operation has completed</returns>
+ public static Task DeleteSecretAsync(this IKvVaultStore store, VaultUserScope user, string path, CancellationToken cancellation)
+ {
+ return store.DeleteSecretAsync(user, path).WaitAsync(cancellation);
+ }
+
+
+ /// <summary>
+ /// Gets a secret from the vault at the specified path and user scope
+ /// </summary>
+ /// <param name="user">The user scope to get the value from</param>
+ /// <param name="path">The secret path</param>
+ /// <param name="cancellation">A token to cancel the operation</param>
+ /// <returns>A task that resolves the secret if found, null otherwise</returns>
+ public static Task<PrivateString?> GetSecretAsync(this IKvVaultStore store, VaultUserScope user, string path, CancellationToken cancellation)
+ {
+ return store.GetSecretAsync(user, path).WaitAsync(cancellation);
+ }
+
+
+ /// <summary>
+ /// Sets a secret in the vault at the specified path and user scope
+ /// </summary>
+ /// <param name="user">The user scope to store the value at</param>
+ /// <param name="path">The path to the secret</param>
+ /// <param name="secret">The secret value to set</param>
+ /// <param name="cancellation">The cancellation token</param>
+ /// <returns>A task that resolves when the secret has been updated</returns>
+ public static Task SetSecretAsync(this IKvVaultStore store, VaultUserScope user, string path, PrivateString secret, CancellationToken cancellation)
+ {
+ return store.SetSecretAsync(user, path, secret).WaitAsync(cancellation);
+ }
+
+
+ }
+} \ No newline at end of file
diff --git a/back-end/libs/NVault.VaultExtensions/src/VaultUserScope.cs b/back-end/libs/NVault.VaultExtensions/src/VaultUserScope.cs
new file mode 100644
index 0000000..b70028e
--- /dev/null
+++ b/back-end/libs/NVault.VaultExtensions/src/VaultUserScope.cs
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 Vaughn Nugent
+//
+// This program 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.
+//
+// This program 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/>.
+
+namespace NVault.VaultExtensions
+{
+ /// <summary>
+ /// Represents a user scope for the vault. It isolates the user's
+ /// secrets from other users.
+ /// </summary>
+ /// <param name="UserId">The id of the user to scope the vault to</param>
+ public readonly record struct VaultUserScope(string UserId)
+ { }
+} \ No newline at end of file