From 87645bfad3943e1110e4cb2e038124083e8ae793 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sun, 28 Jan 2024 19:56:02 -0500 Subject: progress update --- lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs | 107 ++++++++ .../src/NVault.Crypto.Noscrypt.csproj | 27 ++ .../src/ContextExtensions.cs | 274 ++++++++++++++++++++ lib/NVault.Crypto.Secp256k1/src/IRandomSource.cs | 32 +++ lib/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs | 284 +++++++++++++++++++++ .../src/NVault.Crypto.Secp256k1.csproj | 27 ++ .../src/Secp256HashFuncState.cs | 56 ++++ .../src/Secp256k1Context.cs | 76 ++++++ .../src/Secp256k1SecretKey.cs | 58 +++++ .../src/UnmanagedRandomSource.cs | 101 ++++++++ .../src/IClientAccessScope.cs | 48 ++++ lib/NVault.VaultExtensions/src/IKvVaultStore.cs | 52 ++++ .../src/IVaultClientScope.cs | 33 +++ .../src/IVaultKvClientScope.cs | 29 +++ lib/NVault.VaultExtensions/src/KvVaultStorage.cs | 66 +++++ .../src/NVault.VaultExtensions.csproj | 27 ++ .../src/VaultClientExtensions.cs | 156 +++++++++++ lib/NVault.VaultExtensions/src/VaultUserScope.cs | 25 ++ 18 files changed, 1478 insertions(+) create mode 100644 lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs create mode 100644 lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj create mode 100644 lib/NVault.Crypto.Secp256k1/src/ContextExtensions.cs create mode 100644 lib/NVault.Crypto.Secp256k1/src/IRandomSource.cs create mode 100644 lib/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs create mode 100644 lib/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj create mode 100644 lib/NVault.Crypto.Secp256k1/src/Secp256HashFuncState.cs create mode 100644 lib/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs create mode 100644 lib/NVault.Crypto.Secp256k1/src/Secp256k1SecretKey.cs create mode 100644 lib/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs create mode 100644 lib/NVault.VaultExtensions/src/IClientAccessScope.cs create mode 100644 lib/NVault.VaultExtensions/src/IKvVaultStore.cs create mode 100644 lib/NVault.VaultExtensions/src/IVaultClientScope.cs create mode 100644 lib/NVault.VaultExtensions/src/IVaultKvClientScope.cs create mode 100644 lib/NVault.VaultExtensions/src/KvVaultStorage.cs create mode 100644 lib/NVault.VaultExtensions/src/NVault.VaultExtensions.csproj create mode 100644 lib/NVault.VaultExtensions/src/VaultClientExtensions.cs create mode 100644 lib/NVault.VaultExtensions/src/VaultUserScope.cs (limited to 'lib') diff --git a/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs b/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs new file mode 100644 index 0000000..ec0ce2d --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/LibNoscrypt.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +using VNLib.Utils; +using VNLib.Utils.Extensions; +using VNLib.Utils.Native; + +using NCResult = System.Int64; + +namespace NVault.Crypto.Noscrypt +{ + public unsafe sealed class LibNoscrypt(SafeLibraryHandle Library, bool OwnsHandle) : VnDisposeable + { + //Values that match the noscrypt.h header + public const int NC_SEC_KEY_SIZE = 32; + public const int NC_SEC_PUBKEY_SIZE = 32; + public const int NC_ENCRYPTION_NONCE_SIZE = 32; + public const int NC_PUBKEY_SIZE = 32; + public const int NC_SIGNATURE_SIZE = 64; + public const int NC_CONV_KEY_SIZE = 32; + public const int NC_MESSAGE_KEY_SIZE = 32; + public const int CTX_ENTROPY_SIZE = 32; + + //STRUCTS MUST MATCH THE NOSCRYPT.H HEADER + + [StructLayout(LayoutKind.Sequential, Size = NC_SEC_KEY_SIZE)] + internal struct NCSecretKey + { + public fixed byte key[NC_SEC_KEY_SIZE]; + } + + [StructLayout(LayoutKind.Sequential, Size = NC_SEC_PUBKEY_SIZE)] + internal struct NCPublicKey + { + public fixed byte key[NC_SEC_PUBKEY_SIZE]; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct NCCryptoData + { + public fixed byte nonce[NC_ENCRYPTION_NONCE_SIZE]; + public void* inputData; + public void* outputData; + public uint dataSize; + } + + //FUCNTIONS + [SafeMethodName("NCGetContextStructSize")] + internal delegate uint NCGetContextStructSizeDelegate(); + + [SafeMethodName("NCInitContext")] + internal delegate NCResult NCInitContextDelegate(void* ctx , byte* entropy32); + + [SafeMethodName("NCReInitContext")] + internal delegate NCResult NCReInitContextDelegate(void* ctx, byte* entropy32); + + [SafeMethodName("NCDestroyContext")] + internal delegate NCResult NCDestroyContextDelegate(void* ctx); + + [SafeMethodName("NCGetPublicKey")] + internal delegate NCResult NCGetPublicKeyDelegate(void* ctx, NCSecretKey* secKey, NCPublicKey* publicKey); + + [SafeMethodName("NCValidateSecretKey")] + internal delegate NCResult NCValidateSecretKeyDelegate(void* ctx, NCSecretKey* secKey); + + [SafeMethodName("NCSignData")] + internal delegate NCResult NCSignDataDelegate(void* ctx, NCSecretKey* secKey, byte* random32, byte* data, long dataSize, byte* sig64); + + [SafeMethodName("NCVerifyData")] + internal delegate NCResult NCVerifyDataDelegate(void* ctx, NCPublicKey* pubKey, byte* data, long dataSize, byte* sig64); + + [SafeMethodName("NCSignDigest")] + internal delegate NCResult NCSignDigestDelegate(void* ctx, NCSecretKey* secKey, byte* random32, byte* digest32, byte* sig64); + + [SafeMethodName("NCVerifyDigest")] + internal delegate NCResult NCVerifyDigestDelegate(void* ctx, NCPublicKey* pubKey, byte* digest32, byte* sig64); + + + + + /// + protected override void Free() + { + if (OwnsHandle) + { + Library.Dispose(); + } + } + + public static LibNoscrypt Load(string path, DllImportSearchPath search) + { + //Load the native library + SafeLibraryHandle handle = SafeLibraryHandle.LoadLibrary(path, search); + + //Create the wrapper + return new LibNoscrypt(handle, true); + } + + public static LibNoscrypt Load(string path) => Load(path, DllImportSearchPath.SafeDirectories); + + + } +} diff --git a/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj b/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj new file mode 100644 index 0000000..2416535 --- /dev/null +++ b/lib/NVault.Crypto.Noscrypt/src/NVault.Crypto.Noscrypt.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + true + README.md + NVault.Crypto.Noscrypt + NVault.Crypto.Noscrypt + + + + Vaughn Nugent + Vaughn Nugent + NVault.Crypto.Noscrypt + Provides a managed library for the noscrypt native library, along with other helper types for NVault + Copyright © 2024 Vaughn Nugent + https://www.vaughnnugent.com/resources/software/modules/NVault + https://github.com/VnUgE/NVault/tree/master/ + + + + + + + + diff --git a/lib/NVault.Crypto.Secp256k1/src/ContextExtensions.cs b/lib/NVault.Crypto.Secp256k1/src/ContextExtensions.cs new file mode 100644 index 0000000..bb014df --- /dev/null +++ b/lib/NVault.Crypto.Secp256k1/src/ContextExtensions.cs @@ -0,0 +1,274 @@ +// Copyright (C) 2024 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 . + +using System; +using System.Security.Cryptography; +using System.Runtime.InteropServices; + +using VNLib.Hashing; +using VNLib.Utils; +using VNLib.Utils.Memory; +using VNLib.Utils.Extensions; + +using static NVault.Crypto.Secp256k1.LibSecp256k1; + +namespace NVault.Crypto.Secp256k1 +{ + /// + /// The callback function signature required for ECDH hash functions + /// + /// The callback state + /// The return value to be passed as a result of the operation + public delegate int Secp256k1EcdhHashFunc(in Secp256HashFuncState state); + + public static unsafe class ContextExtensions + { + /// + /// Signs a 32byte message digest with the specified secret key on the current context and writes the signature to the specified buffer + /// + /// + /// The 32byte secret key used to sign messages from the user + /// The 32byte message digest to compute the signature of + /// The buffer to write the signature output to, must be at-least 64 bytes + /// The number of bytes written to the signature buffer, or less than 1 if the operation failed + public static ERRNO SchnorSignDigest(this in Secp256k1Context context, ReadOnlySpan secretKey, ReadOnlySpan digest, Span 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; + } + + //Secret key size must be exactly the size of the secret key struct + if(secretKey.Length != sizeof(Secp256k1SecretKey)) + { + return ERRNO.E_FAIL; + } + + //Stack allocated keypair + KeyPair keyPair = new(); + + //Init the secret key struct from key data + Secp256k1SecretKey secKeyStruct = MemoryMarshal.Read(secretKey); + + //Randomize the context and create the keypair + if (!context.CreateKeyPair(&keyPair, &secKeyStruct)) + { + return ERRNO.E_FAIL; + } + + //Create the random nonce + byte* random = stackalloc byte[RandomBufferSize]; + + //Fill the buffer with random bytes + context.Lib.GetRandomBytes(new Span(random, RandomBufferSize)); + + try + { + fixed (byte* sigPtr = &MemoryMarshal.GetReference(signature), + digestPtr = &MemoryMarshal.GetReference(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; + } + + /// + /// Generates an x-only Schnor encoded public key from the specified secret key on the + /// current context and writes it to the specified buffer. + /// + /// + /// The 32byte secret key used to derrive the public key from + /// The buffer to write the x-only Schnor encoded public key + /// The number of bytes written to the output buffer, or 0 if the operation failed + /// + public static ERRNO GeneratePubKeyFromSecret(this in Secp256k1Context context, ReadOnlySpan secretKey, Span pubKeyBuffer) + { + if (secretKey.Length != sizeof(Secp256k1SecretKey)) + { + throw new CryptographicException($"Your secret key must be exactly {sizeof(Secp256k1SecretKey)} 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 + Secp256k1PublicKey xOnlyPubKey = new(); + Secp256k1SecretKey secKeyStruct = MemoryMarshal.Read(secretKey); + KeyPair keyPair = new(); + + try + { + //Init context and keypair + if (!context.CreateKeyPair(&keyPair, &secKeyStruct)) + { + 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 = &MemoryMarshal.GetReference(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; + } + + /// + /// Verifies that a given secret key is valid using the current context + /// + /// + /// The secret key to verify + /// A boolean value that indicates if the secret key is valid or not + /// + public static bool VerifySecretKey(this in Secp256k1Context context, ReadOnlySpan secretKey) + { + if (secretKey.Length != sizeof(Secp256k1SecretKey)) + { + throw new CryptographicException($"Your secret key must be exactly {sizeof(Secp256k1SecretKey)} bytes long"); + } + + context.Lib.SafeLibHandle.ThrowIfClosed(); + + //Get sec key ref and verify + fixed(byte* ptr = &MemoryMarshal.GetReference(secretKey)) + { + return context.Lib._secKeyVerify.Invoke(context.Context, ptr) == 1; + } + } + + + [StructLayout(LayoutKind.Sequential)] + private readonly ref struct EcdhHashFuncState + { + public readonly IntPtr HashFunc { get; init; } + public readonly IntPtr Opaque { get; init; } + public readonly int OutLen { get; init; } + } + + /// + /// Verifies that a given secret key is valid using the current context + /// + /// + /// The secret key to verify + /// A boolean value that indicates if the secret key is valid or not + /// + public static bool ComputeSharedKey(this in Secp256k1Context context, Span data, ReadOnlySpan xOnlyPubKey, ReadOnlySpan secretKey, Secp256k1EcdhHashFunc callback, IntPtr opaque) + { + if (secretKey.Length != sizeof(Secp256k1SecretKey)) + { + throw new ArgumentException($"Your secret key buffer must be exactly {sizeof(Secp256k1SecretKey)} bytes long"); + } + + //Init callback state struct + EcdhHashFuncState state = new() + { + HashFunc = Marshal.GetFunctionPointerForDelegate(callback), + Opaque = opaque, + OutLen = data.Length + }; + + context.Lib.SafeLibHandle.ThrowIfClosed(); + + //Stack allocated keypair and x-only public key + Secp256k1PublicKey peerPubKey = new(); + + //Parse the public key from the buffer + fixed (byte* pubkeyPtr = &MemoryMarshal.GetReference(xOnlyPubKey)) + { + context.Lib._xOnlyPubkeyParse(context.Context, &peerPubKey, pubkeyPtr); + } + + fixed (byte* dataPtr = &MemoryMarshal.GetReference(data), + secKeyPtr = &MemoryMarshal.GetReference(secretKey)) + { + return context.Lib._ecdh.Invoke( + context.Context, + dataPtr, + &peerPubKey, + secKeyPtr, + UmanagedEcdhHashFuncCallback, + &state + ) == 1; + } + + /* + * Umanaged wrapper function for invoking the safe user callback + * from the unmanaged lib + */ + static int UmanagedEcdhHashFuncCallback(byte* output, byte* x32, byte* y32, void* opaque) + { + //Recover the callback + if (opaque == null) + { + return 0; + } + + EcdhHashFuncState* state = (EcdhHashFuncState*)opaque; + + //Init user-state structure + Secp256HashFuncState userState = new(output, state->OutLen, x32, 32, new(opaque)); + + //Recover the function pointer + Secp256k1EcdhHashFunc callback = Marshal.GetDelegateForFunctionPointer(state->HashFunc); + + //Invoke the callback + return callback(in userState); + } + } + } +} \ No newline at end of file diff --git a/lib/NVault.Crypto.Secp256k1/src/IRandomSource.cs b/lib/NVault.Crypto.Secp256k1/src/IRandomSource.cs new file mode 100644 index 0000000..4e1861d --- /dev/null +++ b/lib/NVault.Crypto.Secp256k1/src/IRandomSource.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2024 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 . + +using System; + +namespace NVault.Crypto.Secp256k1 +{ + /// + /// Represents a generator for random data, that fills abinary buffer with random bytes + /// on demand. + /// + public interface IRandomSource + { + /// + /// Fills the given buffer with random bytes + /// + /// Binary buffer to fill with random data + void GetRandomBytes(Span buffer); + } +} \ No newline at end of file diff --git a/lib/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs b/lib/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs new file mode 100644 index 0000000..8dda269 --- /dev/null +++ b/lib/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs @@ -0,0 +1,284 @@ +// Copyright (C) 2024 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 . + +using System; +using System.Runtime.InteropServices; + +using VNLib.Hashing; +using VNLib.Utils; +using VNLib.Utils.Native; +using VNLib.Utils.Extensions; + +namespace NVault.Crypto.Secp256k1 +{ + + internal unsafe delegate int EcdhHasFunc(byte* output, byte* x32, byte* y32, void* data); + + public unsafe class LibSecp256k1 : VnDisposeable + { + public const int SignatureSize = 64; + public const int RandomBufferSize = 32; + public const int XOnlyPublicKeySize = 32; + + public static readonly int SecretKeySize = sizeof(Secp256k1SecretKey); + + /* + * Unsafe structures that represent the native keypair and x-only public key + * structures. They hold character arrays + */ + [StructLayout(LayoutKind.Sequential, Size = 96)] + internal struct KeyPair + { + public fixed byte data[96]; + } + + /// + /// 1:1 with the secp256k1_pubkey structure + /// + [StructLayout(LayoutKind.Sequential, Size = 64)] + internal struct Secp256k1PublicKey + { + public fixed byte data[64]; + } + + + //Native methods + [SafeMethodName("secp256k1_context_create")] + internal delegate IntPtr ContextCreate(int flags); + + [SafeMethodName("secp256k1_context_destroy")] + internal delegate void ContextDestroy(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, Secp256k1PublicKey* pubkey, int pk_parity, KeyPair* keypair); + + [SafeMethodName("secp256k1_xonly_pubkey_serialize")] + internal delegate int XOnlyPubkeySerialize(IntPtr ctx, byte* output32, Secp256k1PublicKey* pubkey); + + [SafeMethodName("secp256k1_schnorrsig_sign32")] + internal delegate int SignHash(IntPtr ctx, byte* sig64, byte* msg32, KeyPair* keypair, byte* aux_rand32); + + [SafeMethodName("secp256k1_ec_seckey_verify")] + internal delegate int SecKeyVerify(IntPtr ctx, in byte* seckey); + + [SafeMethodName("secp256k1_ec_pubkey_serialize")] + internal delegate int PubKeySerialize(IntPtr ctx, byte* outPubKey, ulong* outLen, Secp256k1PublicKey* pubKey, uint flags); + + [SafeMethodName("secp256k1_xonly_pubkey_parse")] + internal delegate int XOnlyPubkeyParse(IntPtr ctx, Secp256k1PublicKey* pubkey, byte* input32); + + [SafeMethodName("secp256k1_ecdh")] + internal delegate int Ecdh( + IntPtr ctx, + byte* output, + Secp256k1PublicKey* pubkey, + byte* scalar, + EcdhHasFunc hashFunc, + void* dataPtr + ); + + + /// + /// Loads the Secp256k1 library from the specified path and creates a wrapper class (loads methods from the library) + /// + /// The realtive or absolute path to the shared library + /// The DLL probing path pattern + /// The library wrapper class + /// + /// + /// + /// + 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; + } + } + + /// + /// Loads the Secp256k1 library from the specified path and creates a wrapper class (loads methods from the library) + /// + /// The handle to the shared library + /// An optional random source to create random entropy and secrets from + /// The library wrapper class + /// + /// + /// + public static LibSecp256k1 FromHandle(SafeLibraryHandle handle, IRandomSource? random) + { + _ = handle ?? throw new ArgumentNullException(nameof(handle)); + //setup fallback random source if null + random ??= new FallbackRandom(); + //Create the lib + return new LibSecp256k1(handle, random); + } + + /// + /// The underlying library handle + /// + public SafeLibraryHandle SafeLibHandle { get; } + + internal readonly KeypairCreate _createKeyPair; + internal readonly ContextCreate _create; + internal readonly RandomizeContext _randomize; + internal readonly ContextDestroy _destroy; + internal readonly KeypairXOnlyPub _createXonly; + internal readonly XOnlyPubkeySerialize _serializeXonly; + internal readonly SignHash _signHash; + internal readonly SecKeyVerify _secKeyVerify; + internal readonly PubKeySerialize _pubKeySerialize; + internal readonly Ecdh _ecdh; + internal readonly XOnlyPubkeyParse _xOnlyPubkeyParse; + private readonly IRandomSource _randomSource; + + /// + /// Creates a new instance of the class from the specified library handle + /// + /// The library handle that referrences the secp256k1 platform specific library + /// + /// This method attempts to capture all the native methods from the library, which may throw if the library is not valid. + /// + /// + /// + /// + 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(); + _createKeyPair = handle.DangerousGetMethod(); + _randomize = handle.DangerousGetMethod(); + _destroy = handle.DangerousGetMethod(); + _createXonly = handle.DangerousGetMethod(); + _serializeXonly = handle.DangerousGetMethod(); + _signHash = handle.DangerousGetMethod(); + _secKeyVerify = handle.DangerousGetMethod(); + _pubKeySerialize = handle.DangerousGetMethod(); + _ecdh = handle.DangerousGetMethod(); + _xOnlyPubkeyParse = handle.DangerousGetMethod(); + + //Store random source + _randomSource = randomSource; + } + + /// + /// Creates a new instance of the class from the specified library handle + /// with a fallback random source + /// + /// The library handle + /// + /// + /// + public LibSecp256k1(SafeLibraryHandle handle):this(handle, new FallbackRandom()) + {} + + /// + /// Generates a new secret key and writes it to the specified buffer. The buffer size must be exactly bytes long + /// + /// NOTE: You should verify this validity of the key against the library with a new + /// + /// + /// The secret key buffer + /// + public void CreateSecretKey(Span buffer) + { + //Protect for released lib + SafeLibHandle.ThrowIfClosed(); + + if(buffer.Length != sizeof(Secp256k1SecretKey)) + { + throw new ArgumentException($"Buffer must be exactly {sizeof(Secp256k1SecretKey)} bytes long", nameof(buffer)); + } + + //Fill the buffer with random bytes + _randomSource.GetRandomBytes(buffer); + } + + /// + /// Fills the given buffer with random bytes from + /// the internal random source + /// + /// The buffer to fill with random data + public void GetRandomBytes(Span buffer) + { + //Protect for released lib + SafeLibHandle.ThrowIfClosed(); + + _randomSource.GetRandomBytes(buffer); + } + + /// + /// Creates a new from the current managed library + /// + /// + /// The new object from the library + /// + public Secp256k1Context CreateContext() + { + //Protect for released lib + SafeLibHandle.ThrowIfClosed(); + + //Create new context + IntPtr context = _create(1); + + if (context == IntPtr.Zero) + { + throw new OutOfMemoryException("Failed to create the new Secp256k1 context"); + } + + return new Secp256k1Context(this, context); + } + + protected override void Free() + { + //Free native library + SafeLibHandle.Dispose(); + } + + private record class FallbackRandom : IRandomSource + { + public void GetRandomBytes(Span buffer) + { + //Use the random generator from the crypto lib + RandomHash.GetRandomBytes(buffer); + } + } + } +} \ No newline at end of file diff --git a/lib/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj b/lib/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj new file mode 100644 index 0000000..efd78ab --- /dev/null +++ b/lib/NVault.Crypto.Secp256k1/src/NVault.Crypto.Secp256k1.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + true + README.md + NVault.Crypto.Secp256k1 + NVault.Crypto.Secp256k1 + + + + Vaughn Nugent + Vaughn Nugent + NVault.Crypto.Secp256k1 + Provides a managed library for the Bitcoin Core secp256k1 library, along with other helper types for NVault + Copyright © 2024 Vaughn Nugent + https://www.vaughnnugent.com/resources/software/modules/NVault + https://github.com/VnUgE/NVault/tree/master/ + + + + + + + + diff --git a/lib/NVault.Crypto.Secp256k1/src/Secp256HashFuncState.cs b/lib/NVault.Crypto.Secp256k1/src/Secp256HashFuncState.cs new file mode 100644 index 0000000..c82321c --- /dev/null +++ b/lib/NVault.Crypto.Secp256k1/src/Secp256HashFuncState.cs @@ -0,0 +1,56 @@ +// Copyright (C) 2024 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 . + +using System; +using System.Runtime.InteropServices; + +namespace NVault.Crypto.Secp256k1 +{ + [StructLayout(LayoutKind.Sequential)] + public unsafe readonly ref struct Secp256HashFuncState + { + + /// + /// The opaque pointer passed to the hash function + /// + public readonly IntPtr Opaque { get; } + + private readonly byte* _output; + private readonly byte* _xCoord; + private readonly int _outputLength; + private readonly int _xCoordLength; + + internal Secp256HashFuncState(byte* output, int outputLength, byte* xCoord, int xCoordLength, IntPtr opaque) + { + Opaque = opaque; + _output = output; + _outputLength = outputLength; + _xCoord = xCoord; + _xCoordLength = xCoordLength; + } + + /// + /// Gets the output buffer as a span + /// + /// The output buffer span + public readonly Span GetOutput() => new(_output, _outputLength); + + /// + /// Gets the x coordinate argument as a span + /// + /// The xcoordinate buffer span + public readonly ReadOnlySpan GetXCoordArg() => new(_xCoord, _xCoordLength); + } +} \ No newline at end of file diff --git a/lib/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs b/lib/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs new file mode 100644 index 0000000..dfb3ff8 --- /dev/null +++ b/lib/NVault.Crypto.Secp256k1/src/Secp256k1Context.cs @@ -0,0 +1,76 @@ +// Copyright (C) 2024 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 . + +using System; + +using VNLib.Utils.Extensions; +using VNLib.Utils.Memory; + +using static NVault.Crypto.Secp256k1.LibSecp256k1; + +namespace NVault.Crypto.Secp256k1 +{ + /// + /// Represents a Secp256k1 context, it is used to randomize the instance, create key pairs, + /// and frees the context when disposed + /// + /// The library instance + /// A pointer to the initialized context instance + public readonly record struct Secp256k1Context(LibSecp256k1 Lib, IntPtr Context) : IDisposable + { + /// + /// Randomizes the context with random data using the library's random source + /// + /// True if the context was successfully randomized, false otherwise + public unsafe readonly bool Randomize() + { + Lib.SafeLibHandle.ThrowIfClosed(); + + //Randomze the context + byte* entropy = stackalloc byte[RandomBufferSize]; + + //Get random bytes + Lib.GetRandomBytes(new Span(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, Secp256k1SecretKey* secretKey) + { + Lib.SafeLibHandle.ThrowIfClosed(); + + //Create the keypair from the secret key + return Lib._createKeyPair(Context, keyPair, (byte*)secretKey) == 1; + } + + /// + /// Releases the context instance and frees the memory + /// + public readonly void Dispose() + { + if (Context != IntPtr.Zero) + { + //Free the context + Lib._destroy(Context); + } + } + } +} \ No newline at end of file diff --git a/lib/NVault.Crypto.Secp256k1/src/Secp256k1SecretKey.cs b/lib/NVault.Crypto.Secp256k1/src/Secp256k1SecretKey.cs new file mode 100644 index 0000000..35734ae --- /dev/null +++ b/lib/NVault.Crypto.Secp256k1/src/Secp256k1SecretKey.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2024 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 . + +using System; +using System.Runtime.InteropServices; + +using VNLib.Utils.Memory; + +namespace NVault.Crypto.Secp256k1 +{ + /// + /// Represents a Secp256k1 secret key, the size is fixed, and should use + /// the sizeof() operator to get the size + /// + [StructLayout(LayoutKind.Sequential, Size = 32)] + public unsafe struct Secp256k1SecretKey + { + private fixed byte data[32]; + + /// + /// Implict cast to a span of raw bytes + /// + /// The secret key to cast + public static implicit operator Span(Secp256k1SecretKey key) => new(key.data, 32); + + /// + /// Casts the secret key span to a via a structure copy + /// + /// The key data to copy + /// + public static explicit operator Secp256k1SecretKey(ReadOnlySpan key) => FromSpan(key); + + /// + /// Creates a new from a span of bytes + /// by copying the bytes into the struct + /// + /// The secret key data to copy + /// An initilaized + public static Secp256k1SecretKey FromSpan(ReadOnlySpan span) + { + Secp256k1SecretKey newKey = new(); + MemoryUtil.CopyStruct(span, ref newKey); + return newKey; + } + } +} \ No newline at end of file diff --git a/lib/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs b/lib/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs new file mode 100644 index 0000000..360de21 --- /dev/null +++ b/lib/NVault.Crypto.Secp256k1/src/UnmanagedRandomSource.cs @@ -0,0 +1,101 @@ +// Copyright (C) 2024 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 . + +using System; +using System.Runtime.InteropServices; + +using VNLib.Utils; +using VNLib.Utils.Native; +using VNLib.Utils.Extensions; + +namespace NVault.Crypto.Secp256k1 +{ + + /// + /// A wrapper class for an unmanaged random source that conforms to the interface + /// + 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; + + /// + /// Loads the unmanaged random source from the given library + /// and attempts to get the random bytes method + /// + /// + /// + /// The wrapped library that conforms to the + 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; + } + } + + /// + /// Creates the unmanaged random source from the given library + /// + /// The library handle to wrap + /// + /// + public UnmanagedRandomSource(SafeLibraryHandle lib, bool ownsHandle) + { + lib.ThrowIfClosed(); + + _library = lib; + + //get the method delegate + _getRandomBytes = lib.DangerousGetMethod(METHOD_NAME); + + OwnsHandle = ownsHandle; + } + + public unsafe void GetRandomBytes(Span buffer) + { + _library.ThrowIfClosed(); + + //Fix buffer and call unmanaged method + fixed(byte* ptr = &MemoryMarshal.GetReference(buffer)) + { + _getRandomBytes(ptr, buffer.Length); + } + } + + /// + protected override void Free() + { + if (OwnsHandle) + { + _library.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/lib/NVault.VaultExtensions/src/IClientAccessScope.cs b/lib/NVault.VaultExtensions/src/IClientAccessScope.cs new file mode 100644 index 0000000..c79f75e --- /dev/null +++ b/lib/NVault.VaultExtensions/src/IClientAccessScope.cs @@ -0,0 +1,48 @@ +// Copyright (C) 2024 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 . + +using System; +using System.Collections.Generic; + +namespace NVault.VaultExtensions +{ + /// + /// Represents a user auth token access scope + /// configuration. + /// + public interface IClientAccessScope + { + /// + /// The list of policies for new token generation + /// + IList Policies { get; } + + /// + /// Allows the user to renew the access token + /// + bool Renewable { get; } + + /// + /// The token + /// + string TokenTtl { get; } + + /// + /// The explicit number of token uses allowed by the genreated token, + /// 0 for unlimited uses + /// + int NumberOfUses { get; } + } +} \ No newline at end of file diff --git a/lib/NVault.VaultExtensions/src/IKvVaultStore.cs b/lib/NVault.VaultExtensions/src/IKvVaultStore.cs new file mode 100644 index 0000000..037fe6c --- /dev/null +++ b/lib/NVault.VaultExtensions/src/IKvVaultStore.cs @@ -0,0 +1,52 @@ +// Copyright (C) 2024 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 . + +using System.Threading.Tasks; + +using VNLib.Utils.Memory; + +namespace NVault.VaultExtensions +{ + /// + /// Represents a vault key-value store that can be used to store secrets + /// + public interface IKvVaultStore + { + /// + /// Deletes a secret from the vault + /// + /// The user scope of the secret + /// The path to the secret + /// A task that returns when the operation has completed + Task DeleteSecretAsync(VaultUserScope user, string path); + + /// + /// Sets a secret in the vault at the specified path and user scope + /// + /// The user scope to store the value at + /// The path to the secret + /// The secret value to set + /// A task that resolves when the secret has been updated + Task SetSecretAsync(VaultUserScope user, string path, PrivateString secret); + + /// + /// Gets a secret from the vault at the specified path and user scope + /// + /// The user scope to get the value from + /// The secret path + /// A task that resolves the secret if found, null otherwise + Task GetSecretAsync(VaultUserScope user, string path); + } +} \ No newline at end of file diff --git a/lib/NVault.VaultExtensions/src/IVaultClientScope.cs b/lib/NVault.VaultExtensions/src/IVaultClientScope.cs new file mode 100644 index 0000000..d53bc4a --- /dev/null +++ b/lib/NVault.VaultExtensions/src/IVaultClientScope.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2024 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 . + +namespace NVault.VaultExtensions +{ + /// + /// Represents a vault client scope configuration + /// + public interface IVaultClientScope + { + /// + /// The mount point for the vault + /// + string? MountPoint { get; } + + /// + /// The entry path for the vault + /// + string? EntryPath { get; } + } +} \ No newline at end of file diff --git a/lib/NVault.VaultExtensions/src/IVaultKvClientScope.cs b/lib/NVault.VaultExtensions/src/IVaultKvClientScope.cs new file mode 100644 index 0000000..f763473 --- /dev/null +++ b/lib/NVault.VaultExtensions/src/IVaultKvClientScope.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2024 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 . + +namespace NVault.VaultExtensions +{ + /// + /// A key-value specific scoped client + /// + public interface IVaultKvClientScope : IVaultClientScope + { + /// + /// The property to store the secret value in the + /// storage dictionary + /// + string StorageProperty { get; } + } +} \ No newline at end of file diff --git a/lib/NVault.VaultExtensions/src/KvVaultStorage.cs b/lib/NVault.VaultExtensions/src/KvVaultStorage.cs new file mode 100644 index 0000000..8a2b9b6 --- /dev/null +++ b/lib/NVault.VaultExtensions/src/KvVaultStorage.cs @@ -0,0 +1,66 @@ +// Copyright (C) 2024 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 . + +using System.Threading.Tasks; + +using VaultSharp; + +using VNLib.Utils.Memory; + +namespace NVault.VaultExtensions +{ + /// + /// An abstract kv storage implementation that uses the vault client to store secrets + /// + public abstract class KvVaultStorage : IKvVaultStore + { + /// + /// The vault client + /// + protected abstract IVaultClient Client { get; } + + /// + /// The storage scope + /// + 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 GetSecretAsync(VaultUserScope user, string path) + { + string tPath = TranslatePath(path); + return Client.GetSecretAsync(Scope, user, tPath); + } + + /// + /// 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 + /// + /// The item path to scope + /// The further scoped vault path for the item + public virtual string TranslatePath(string path) => path; + } +} \ No newline at end of file diff --git a/lib/NVault.VaultExtensions/src/NVault.VaultExtensions.csproj b/lib/NVault.VaultExtensions/src/NVault.VaultExtensions.csproj new file mode 100644 index 0000000..e5dbe8c --- /dev/null +++ b/lib/NVault.VaultExtensions/src/NVault.VaultExtensions.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + true + README.md + NVault.VaultExtensions + NVault.VaultExtensions + + + + Vaughn Nugent + Vaughn Nugent + NVault.VaultExtensions + A Hashicorp Vault unified extension library for NVault + Copyright © 2024 Vaughn Nugent + https://www.vaughnnugent.com/resources/software/modules/NVault + https://github.com/VnUgE/NVault/tree/master/ + + + + + + + + diff --git a/lib/NVault.VaultExtensions/src/VaultClientExtensions.cs b/lib/NVault.VaultExtensions/src/VaultClientExtensions.cs new file mode 100644 index 0000000..d90941a --- /dev/null +++ b/lib/NVault.VaultExtensions/src/VaultClientExtensions.cs @@ -0,0 +1,156 @@ +// Copyright (C) 2024 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 . + +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 GetSecretAsync(this IVaultClient client, IVaultKvClientScope scope, VaultUserScope user, string path) + { + return GetSecretAsync(client, scope, user, path, scope.StorageProperty); + } + + public static async Task 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 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 : PrivateString.ToPrivateString(value, true); + } + + /// + /// Writes a secret to the vault that is scoped by the vault scope, and the user scope. + /// + /// + /// The client scope configuration + /// The user scope to isolate the + /// The item path within the current scope + /// The secret value to set at the desired property + /// A task that resolves when the secret has been updated + public static async Task SetSecretAsync(this IVaultClient client, IVaultKvClientScope scope, VaultUserScope user, string path, PrivateString secret) + { + Dictionary 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); + } + + /// + /// Writes a secret to the vault that is scoped by the vault scope, and the user scope. + /// + /// + /// The client scope configuration + /// The user scope to isolate the + /// The item path within the current scope + /// The secret value to set at the desired property + /// A task that resolves when the secret has been updated + public static async Task SetSecretAsync(this IVaultClient client, IVaultClientScope scope, VaultUserScope user, string path, IDictionary secret) + { + //Get the path complete path for the scope + string fullPath = GetKeyPath(scope, user, path); + + //Get the secret from the vault + Secret result = await client.V1.Secrets.KeyValue.V2.WriteSecretAsync(fullPath, secret, mountPoint:scope.MountPoint); + + return result.Data; + } + + /// + /// Deletes a secret from the vault that is scoped by the vault scope, and the user scope. + /// + /// + /// The client scope + /// The vault user scope + /// The path to the storage + /// A task that resolves when the delete operation has completed + 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); + } + + /// + /// Deletes a secret from the vault + /// + /// The user scope of the secret + /// The path to the secret + /// A token to cancel the operation + /// A task that returns when the operation has completed + public static Task DeleteSecretAsync(this IKvVaultStore store, VaultUserScope user, string path, CancellationToken cancellation) + { + return store.DeleteSecretAsync(user, path).WaitAsync(cancellation); + } + + + /// + /// Gets a secret from the vault at the specified path and user scope + /// + /// The user scope to get the value from + /// The secret path + /// A token to cancel the operation + /// A task that resolves the secret if found, null otherwise + public static Task GetSecretAsync(this IKvVaultStore store, VaultUserScope user, string path, CancellationToken cancellation) + { + return store.GetSecretAsync(user, path).WaitAsync(cancellation); + } + + + /// + /// Sets a secret in the vault at the specified path and user scope + /// + /// The user scope to store the value at + /// The path to the secret + /// The secret value to set + /// The cancellation token + /// A task that resolves when the secret has been updated + 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/lib/NVault.VaultExtensions/src/VaultUserScope.cs b/lib/NVault.VaultExtensions/src/VaultUserScope.cs new file mode 100644 index 0000000..0e8796c --- /dev/null +++ b/lib/NVault.VaultExtensions/src/VaultUserScope.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2024 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 . + +namespace NVault.VaultExtensions +{ + /// + /// Represents a user scope for the vault. It isolates the user's + /// secrets from other users. + /// + /// The id of the user to scope the vault to + public readonly record struct VaultUserScope(string UserId) + { } +} \ No newline at end of file -- cgit