diff options
author | vnugent <public@vaughnnugent.com> | 2024-02-18 13:47:24 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-02-18 13:47:24 -0500 |
commit | 8bf42df576f494f89b6058ad2dd8a9d5cdbff0a1 (patch) | |
tree | 2723a9befa767f0abce9865b112d5833db88b136 /back-end | |
parent | d9f4778896407ebe6e1b8bb439d2c175b4a22f45 (diff) |
refactor: deprecate secp256k1 native, transition to noscrypt library
Diffstat (limited to 'back-end')
-rw-r--r-- | back-end/plugins/nvault/src/INostrCryptoProvider.cs | 10 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/ManagedCryptoprovider.cs | 16 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/Model/NostrContext.cs | 2 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/Model/NostrEventEntry.cs | 5 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/Model/NostrKeyMeta.cs | 9 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/Model/NostrRelay.cs | 10 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/NVault.csproj | 9 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/NativeSecp256k1Library.cs | 217 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/NoscryptProvider.cs | 148 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/NostrOpProvider.cs | 91 | ||||
-rw-r--r-- | back-end/plugins/nvault/src/Properties/launchSettings.json | 8 |
11 files changed, 223 insertions, 302 deletions
diff --git a/back-end/plugins/nvault/src/INostrCryptoProvider.cs b/back-end/plugins/nvault/src/INostrCryptoProvider.cs index c65d3e9..797e034 100644 --- a/back-end/plugins/nvault/src/INostrCryptoProvider.cs +++ b/back-end/plugins/nvault/src/INostrCryptoProvider.cs @@ -28,14 +28,14 @@ namespace NVault.Plugins.Vault int GetSignatureBufferSize(); /// <summary> - /// Signs a message digest with the specified private key and writes + /// Signs a message with the specified private key and writes /// the signature to the specified buffer. /// </summary> - /// <param name="key"></param> - /// <param name="digest"></param> - /// <param name="signatureBuffer"></param> + /// <param name="key">The secret key used to sign the message</param> + /// <param name="data">The message data to sign</param> + /// <param name="signatureBuffer">The signature output buffer</param> /// <returns>The number of bytes written to the signature buffer, 0 or less if the operation failed</returns> - ERRNO SignMessage(ReadOnlySpan<byte> key, ReadOnlySpan<byte> digest, Span<byte> signatureBuffer); + ERRNO SignData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> signatureBuffer); /// <summary> /// Determines the exact size of the buffer required to hold a key pair during diff --git a/back-end/plugins/nvault/src/ManagedCryptoprovider.cs b/back-end/plugins/nvault/src/ManagedCryptoprovider.cs index fb15b8f..fe585cc 100644 --- a/back-end/plugins/nvault/src/ManagedCryptoprovider.cs +++ b/back-end/plugins/nvault/src/ManagedCryptoprovider.cs @@ -18,11 +18,12 @@ using System.Text.Json; using System.Collections.Generic; using VNLib.Utils; +using VNLib.Utils.Memory; using VNLib.Utils.Logging; using VNLib.Plugins; using VNLib.Plugins.Extensions.Loading; -using NVault.Crypto.Secp256k1; +using NVault.Crypto.Noscrypt; namespace NVault.Plugins.Vault { @@ -59,24 +60,27 @@ namespace NVault.Plugins.Vault string nativePath = config.GetRequiredProperty("lib_crypto", p => p.GetString()!); //Load native library path - _provider = NativeSecp256k1Library.LoadLibrary(nativePath, random); - plugin.Log.Verbose("Loaded native Secp256k1 library from {path}", nativePath); + _provider = NoscryptProvider.LoadLibrary(nativePath, random, MemoryUtil.Shared); + plugin.Log.Verbose("Loaded native Noscrypt library from {path}", nativePath); } ///<inheritdoc/> public int GetSignatureBufferSize() => _provider.GetSignatureBufferSize(); ///<inheritdoc/> - public ERRNO SignMessage(ReadOnlySpan<byte> key, ReadOnlySpan<byte> digest, Span<byte> signatureBuffer) => _provider.SignMessage(key, digest, signatureBuffer); + public ERRNO SignData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> digest, Span<byte> signatureBuffer) + => _provider.SignData(key, digest, signatureBuffer); ///<inheritdoc/> public KeyBufferSizes GetKeyBufferSize() => _provider.GetKeyBufferSize(); ///<inheritdoc/> - public bool TryGenerateKeyPair(Span<byte> publicKey, Span<byte> privateKey) => _provider.TryGenerateKeyPair(publicKey, privateKey); + public bool TryGenerateKeyPair(Span<byte> publicKey, Span<byte> privateKey) + => _provider.TryGenerateKeyPair(publicKey, privateKey); ///<inheritdoc/> - public bool RecoverPublicKey(ReadOnlySpan<byte> privateKey, Span<byte> pubKey) => _provider.RecoverPublicKey(privateKey, pubKey); + public bool RecoverPublicKey(ReadOnlySpan<byte> privateKey, Span<byte> pubKey) + => _provider.RecoverPublicKey(privateKey, pubKey); ///<inheritdoc/> public ERRNO DecryptMessage(ReadOnlySpan<byte> secretKey, ReadOnlySpan<byte> targetKey, ReadOnlySpan<byte> aseIv, ReadOnlySpan<byte> cyphterText, Span<byte> outputBuffer) diff --git a/back-end/plugins/nvault/src/Model/NostrContext.cs b/back-end/plugins/nvault/src/Model/NostrContext.cs index 7dc29d0..bfda43e 100644 --- a/back-end/plugins/nvault/src/Model/NostrContext.cs +++ b/back-end/plugins/nvault/src/Model/NostrContext.cs @@ -21,7 +21,7 @@ using VNLib.Plugins.Extensions.Loading.Sql; namespace NVault.Plugins.Vault.Model { - internal class NostrContext : TransactionalDbContext, IDbTableDefinition + internal class NostrContext : DBContextBase, IDbTableDefinition { public DbSet<NostrRelay> NostrRelays { get; set; } diff --git a/back-end/plugins/nvault/src/Model/NostrEventEntry.cs b/back-end/plugins/nvault/src/Model/NostrEventEntry.cs index 9fa5aa2..b34fd9d 100644 --- a/back-end/plugins/nvault/src/Model/NostrEventEntry.cs +++ b/back-end/plugins/nvault/src/Model/NostrEventEntry.cs @@ -16,6 +16,7 @@ using System; using System.Text.Json; using System.Text.Json.Serialization; +using System.ComponentModel.DataAnnotations; using VNLib.Plugins.Extensions.Data; using VNLib.Plugins.Extensions.Data.Abstractions; @@ -25,6 +26,8 @@ namespace NVault.Plugins.Vault.Model { internal sealed class NostrEventEntry : DbModelBase, IUserEntity { + [Key] + [MaxLength(64)] public override string Id { get; set; } public override DateTime Created { get; set; } @@ -33,8 +36,10 @@ namespace NVault.Plugins.Vault.Model //Never share userids with the client [JsonIgnore] + [MaxLength(64)] public string? UserId { get; set; } + [MaxLength(8000)] public string? EventData { get; set; } public static NostrEventEntry FromEvent(string userId, NostrEvent @event) => new() diff --git a/back-end/plugins/nvault/src/Model/NostrKeyMeta.cs b/back-end/plugins/nvault/src/Model/NostrKeyMeta.cs index 3f0b985..3568b8a 100644 --- a/back-end/plugins/nvault/src/Model/NostrKeyMeta.cs +++ b/back-end/plugins/nvault/src/Model/NostrKeyMeta.cs @@ -28,9 +28,11 @@ namespace NVault.Plugins.Vault.Model internal class NostrKeyMeta : DbModelBase, IUserEntity { [Key] - [MaxLength(50)] + [MaxLength(64)] public override string Id { get; set; } + public override DateTime Created { get; set; } + public override DateTime LastModified { get; set; } [JsonPropertyName("PublicKey")] @@ -41,7 +43,7 @@ namespace NVault.Plugins.Vault.Model [MaxLength(50)] public string? UserId { get; set; } - [MaxLength(100)] + [MaxLength(64)] public string? UserName { get; set; } public void CleanupFromUser() @@ -60,8 +62,7 @@ namespace NVault.Plugins.Vault.Model public void Merge(NostrKeyMeta other) { - if (other == null) - throw new ArgumentNullException(nameof(other)); + ArgumentNullException.ThrowIfNull(other); //We only update username and key value UserName = other.UserName; diff --git a/back-end/plugins/nvault/src/Model/NostrRelay.cs b/back-end/plugins/nvault/src/Model/NostrRelay.cs index ab8cea7..f45897f 100644 --- a/back-end/plugins/nvault/src/Model/NostrRelay.cs +++ b/back-end/plugins/nvault/src/Model/NostrRelay.cs @@ -31,22 +31,24 @@ namespace NVault.Plugins.Vault.Model internal class NostrRelay : DbModelBase, IUserEntity { [Key] - [MaxLength(50)] + [MaxLength(64)] [JsonPropertyName("id")] public override string Id { get; set; } + public override DateTime Created { get; set; } + public override DateTime LastModified { get; set; } [JsonPropertyName("url")] [MaxLength(200)] - public string Url { get; set; } + public string? Url { get; set; } [JsonPropertyName("flags")] public NostrRelayFlags Flags { get; set; } [JsonIgnore] - [MaxLength(50)] - public string UserId { get; set; } + [MaxLength(64)] + public string? UserId { get; set; } public void CleanupFromUser() { diff --git a/back-end/plugins/nvault/src/NVault.csproj b/back-end/plugins/nvault/src/NVault.csproj index e876eec..343fd46 100644 --- a/back-end/plugins/nvault/src/NVault.csproj +++ b/back-end/plugins/nvault/src/NVault.csproj @@ -21,14 +21,13 @@ <ItemGroup> <PackageReference Include="FluentValidation" Version="11.9.0" /> - <PackageReference Include="VNLib.Plugins.Extensions.Data" Version="0.1.0-ci0047" /> - <PackageReference Include="VNLib.Plugins.Extensions.Validation" Version="0.1.0-ci0047" /> - <PackageReference Include="VNLib.Plugins.Extensions.Loading.Sql" Version="0.1.0-ci0047" /> + <PackageReference Include="VNLib.Plugins.Extensions.Data" Version="0.1.0-ci0049" /> + <PackageReference Include="VNLib.Plugins.Extensions.Validation" Version="0.1.0-ci0049" /> + <PackageReference Include="VNLib.Plugins.Extensions.Loading.Sql" Version="0.1.0-ci0049" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\..\..\..\lib\NVault.Crypto.Noscrypt\src\NVault.Crypto.Noscrypt.csproj" /> - <ProjectReference Include="..\..\..\..\lib\NVault.Crypto.Secp256k1\src\NVault.Crypto.Secp256k1.csproj" /> <ProjectReference Include="..\..\..\..\lib\NVault.VaultExtensions\src\NVault.VaultExtensions.csproj" /> </ItemGroup> @@ -44,5 +43,5 @@ </None> </ItemGroup> - + </Project> diff --git a/back-end/plugins/nvault/src/NativeSecp256k1Library.cs b/back-end/plugins/nvault/src/NativeSecp256k1Library.cs deleted file mode 100644 index abbafaf..0000000 --- a/back-end/plugins/nvault/src/NativeSecp256k1Library.cs +++ /dev/null @@ -1,217 +0,0 @@ -// 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 <https://www.gnu.org/licenses/>. - -using System; -using System.Security.Cryptography; -using System.Runtime.InteropServices; - -using NVault.Crypto.Secp256k1; - -using VNLib.Utils; -using VNLib.Utils.Memory; - -namespace NVault.Plugins.Vault -{ - internal sealed class NativeSecp256k1Library : VnDisposeable, INostrCryptoProvider - { - private readonly LibSecp256k1 _lib; - - private NativeSecp256k1Library(LibSecp256k1 lib) - { - _lib = lib; - } - - /// <summary> - /// Loads the native library from the specified path - /// </summary> - /// <param name="libFilePath">The library path</param> - /// <param name="random">The optional random source</param> - /// <returns>The loaded <see cref="NativeSecp256k1Library"/></returns> - public static NativeSecp256k1Library LoadLibrary(string libFilePath, IRandomSource? random) - { - LibSecp256k1 lib = LibSecp256k1.LoadLibrary(libFilePath, DllImportSearchPath.SafeDirectories, random); - return new(lib); - } - - ///<inheritdoc/> - public ERRNO DecryptMessage(ReadOnlySpan<byte> secretKey, ReadOnlySpan<byte> targetKey, ReadOnlySpan<byte> aesIv, ReadOnlySpan<byte> ciphterText, Span<byte> outputBuffer) - { - Check(); - //Start with new context - using Secp256k1Context context = _lib.CreateContext(); - - //Randomize context - if (!context.Randomize()) - { - return false; - } - - //Get shared key - byte[] sharedKeyBuffer = new byte[32]; - - try - { - //Get the Secp256k1 shared key - context.ComputeSharedKey(sharedKeyBuffer, targetKey, secretKey, HashFuncCallback, IntPtr.Zero); - - //Init the AES cipher - using Aes aes = Aes.Create(); - aes.Key = sharedKeyBuffer; - aes.Mode = CipherMode.CBC; - - return aes.DecryptCbc(ciphterText, aesIv, outputBuffer, PaddingMode.Zeros); - } - finally - { - //Zero out buffers - MemoryUtil.InitializeBlock(sharedKeyBuffer); - } - } - - ///<inheritdoc/> - public ERRNO EncryptMessage(ReadOnlySpan<byte> secretKey, ReadOnlySpan<byte> targetKey, ReadOnlySpan<byte> aesIv, ReadOnlySpan<byte> plainText, Span<byte> cipherText) - { - Check(); - //Start with new context - using Secp256k1Context context = _lib.CreateContext(); - - //Randomize context - if (!context.Randomize()) - { - return false; - } - - //Get shared key - byte[] sharedKeyBuffer = new byte[32]; - - try - { - //Get the Secp256k1 shared key - if(!context.ComputeSharedKey(sharedKeyBuffer, targetKey, secretKey, HashFuncCallback, IntPtr.Zero)) - { - return ERRNO.E_FAIL; - } - - //Init the AES cipher - using Aes aes = Aes.Create(); - aes.Key = sharedKeyBuffer; - aes.Mode = CipherMode.CBC; - - return aes.EncryptCbc(plainText, aesIv, cipherText, PaddingMode.Zeros); - } - finally - { - //Zero out buffers - MemoryUtil.InitializeBlock(sharedKeyBuffer); - } - } - - static int HashFuncCallback(in Secp256HashFuncState state) - { - //Get function args - Span<byte> sharedKey = state.GetOutput(); - ReadOnlySpan<byte> xCoord = state.GetXCoordArg(); - - //Nostr literally just uses the shared x coord as the shared key - xCoord.CopyTo(sharedKey); - - return xCoord.Length; - } - - //Key sizes are constant - ///<inheritdoc/> - public KeyBufferSizes GetKeyBufferSize() => new(LibSecp256k1.SecretKeySize, LibSecp256k1.XOnlyPublicKeySize); - - //Signature sizes are constant - ///<inheritdoc/> - public int GetSignatureBufferSize() => LibSecp256k1.SignatureSize; - - ///<inheritdoc/> - public bool RecoverPublicKey(ReadOnlySpan<byte> privateKey, Span<byte> pubKey) - { - Check(); - - //Init new context - using Secp256k1Context context = _lib.CreateContext(); - - //Randomize context - if (!context.Randomize()) - { - return false; - } - - //Recover public key from the privatkey - return context.GeneratePubKeyFromSecret(privateKey, pubKey) == LibSecp256k1.XOnlyPublicKeySize; - } - - ///<inheritdoc/> - public ERRNO SignMessage(ReadOnlySpan<byte> key, ReadOnlySpan<byte> digest, Span<byte> signatureBuffer) - { - Check(); - - //Init new context - using Secp256k1Context context = _lib.CreateContext(); - - //Randomize context - if (!context.Randomize()) - { - return false; - } - - //Sign the message - return context.SchnorSignDigest(key, digest, signatureBuffer); - } - - ///<inheritdoc/> - public void GetRandomBytes(Span<byte> bytes) => _lib.GetRandomBytes(bytes); - - ///<inheritdoc/> - public bool TryGenerateKeyPair(Span<byte> publicKey, Span<byte> privateKey) - { - //Trim buffers to the exact size required to avoid exceptions in the native lib - privateKey = privateKey[..LibSecp256k1.SecretKeySize]; - publicKey = publicKey[..LibSecp256k1.XOnlyPublicKeySize]; - - Check(); - - //Init new context - using Secp256k1Context context = _lib.CreateContext(); - - //Randomize context - if (!context.Randomize()) - { - return false; - } - - do - { - //Create the secret key and verify - _lib.CreateSecretKey(privateKey); - } - while(context.VerifySecretKey(privateKey) == false); - - //Create the public key - return context.GeneratePubKeyFromSecret(privateKey, publicKey) == LibSecp256k1.XOnlyPublicKeySize; - } - - ///<inheritdoc/> - protected override void Free() - { - _lib.Dispose(); - } - - - } -}
\ No newline at end of file diff --git a/back-end/plugins/nvault/src/NoscryptProvider.cs b/back-end/plugins/nvault/src/NoscryptProvider.cs new file mode 100644 index 0000000..51774b0 --- /dev/null +++ b/back-end/plugins/nvault/src/NoscryptProvider.cs @@ -0,0 +1,148 @@ +// 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 <https://www.gnu.org/licenses/>. + +using System; + +using NVault.Crypto.Noscrypt; + +using VNLib.Utils; +using VNLib.Utils.Memory; +using VNLib.Hashing; + + +namespace NVault.Plugins.Vault +{ + internal sealed class NoscryptProvider(IRandomSource random, NostrCrypto noscrypt) : VnDisposeable, INostrCryptoProvider + { + const int MaxInvalidSecKeyAttempts = 10; + + ///<inheritdoc/> + public ERRNO DecryptMessage(ReadOnlySpan<byte> secretKey, ReadOnlySpan<byte> targetKey, ReadOnlySpan<byte> aseIv, ReadOnlySpan<byte> cyphterText, Span<byte> outputBuffer) + { + return false; + } + + ///<inheritdoc/> + public ERRNO EncryptMessage(ReadOnlySpan<byte> secretKey, ReadOnlySpan<byte> targetKey, ReadOnlySpan<byte> aesIv, ReadOnlySpan<byte> plainText, Span<byte> cipherText) + { + return false; + } + + ///<inheritdoc/> + public KeyBufferSizes GetKeyBufferSize() + { + return new() + { + PrivateKeySize = LibNoscrypt.NC_SEC_KEY_SIZE, + PublicKeySize = LibNoscrypt.NC_SEC_PUBKEY_SIZE + }; + } + + ///<inheritdoc/> + public void GetRandomBytes(Span<byte> bytes) => random.GetRandomBytes(bytes); + + ///<inheritdoc/> + public int GetSignatureBufferSize() => LibNoscrypt.NC_SIGNATURE_SIZE; + + ///<inheritdoc/> + public bool RecoverPublicKey(ReadOnlySpan<byte> privateKey, Span<byte> pubKey) + { + ArgumentOutOfRangeException.ThrowIfLessThan(privateKey.Length, LibNoscrypt.NC_SEC_KEY_SIZE, nameof(privateKey)); + ArgumentOutOfRangeException.ThrowIfLessThan(pubKey.Length, LibNoscrypt.NC_SEC_PUBKEY_SIZE, nameof(pubKey)); + + noscrypt.GetPublicKey( + in NCUtil.AsSecretKey(privateKey), + ref NCUtil.AsPublicKey(pubKey) + ); + + return true; + } + + ///<inheritdoc/> + public ERRNO SignData(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, Span<byte> signatureBuffer) + { + //Get a buffer of message entropy for libnoscrypt + Span<byte> entropy = stackalloc byte[32]; + random.GetRandomBytes(entropy); + + ref readonly NCSecretKey secretKey = ref NCUtil.AsSecretKey(key); + noscrypt.SignData(in secretKey, entropy, data, signatureBuffer); + + return LibNoscrypt.NC_SIGNATURE_SIZE; + } + + ///<inheritdoc/> + public bool TryGenerateKeyPair(Span<byte> publicKey, Span<byte> privateKey) + { + ArgumentOutOfRangeException.ThrowIfLessThan(privateKey.Length, LibNoscrypt.NC_SEC_KEY_SIZE, nameof(privateKey)); + ArgumentOutOfRangeException.ThrowIfLessThan(publicKey.Length, LibNoscrypt.NC_SEC_PUBKEY_SIZE, nameof(publicKey)); + + ref readonly NCSecretKey asSecretKey = ref NCUtil.AsSecretKey(privateKey); + ref NCPublicKey asPubKey = ref NCUtil.AsPublicKey(publicKey); + + int loopCount = 0; + + random.GetRandomBytes(privateKey); + + //Validate the secret key data + while (noscrypt.ValidateSecretKey(in asSecretKey) == false) + { + if(loopCount++ > MaxInvalidSecKeyAttempts) + { + return false; + } + + //Try to get random key again + random.GetRandomBytes(privateKey); + } + + //Generate the public key after secret key is validated + noscrypt.GetPublicKey(in asSecretKey, ref asPubKey); + return true; + } + + ///<inheritdoc/> + protected override void Free() => noscrypt.Dispose(); + + + public static NoscryptProvider LoadLibrary(string libPath, IRandomSource? random, IUnmangedHeap heap) + { + //Fallback to platform random crypto + random ??= new PlatformRandom(); + + LibNoscrypt lib = LibNoscrypt.Load(libPath); + + try + { + Span<byte> entropy = stackalloc byte[LibNoscrypt.CTX_ENTROPY_SIZE]; + random.GetRandomBytes(entropy); + + NostrCrypto nostr = lib.InitializeCrypto(heap, entropy); + return new NoscryptProvider(random, nostr); + } + catch + { + lib.Dispose(); + throw; + } + } + + sealed class PlatformRandom() : IRandomSource + { + ///<inheritdoc/> + public void GetRandomBytes(Span<byte> buffer) => RandomHash.GetRandomBytes(buffer); + } + } +}
\ No newline at end of file diff --git a/back-end/plugins/nvault/src/NostrOpProvider.cs b/back-end/plugins/nvault/src/NostrOpProvider.cs index 48ffe93..cc7342a 100644 --- a/back-end/plugins/nvault/src/NostrOpProvider.cs +++ b/back-end/plugins/nvault/src/NostrOpProvider.cs @@ -36,28 +36,16 @@ using NVault.Plugins.Vault.Model; namespace NVault.Plugins.Vault { - internal sealed class NostrOpProvider : INostrOperations + internal sealed class NostrOpProvider(PluginBase plugin) : INostrOperations { public const int AES_IV_SIZE = 16; public static int IvMaxBase64EncodedSize { get; } = Base64.GetMaxEncodedToUtf8Length(AES_IV_SIZE); - private static JavaScriptEncoder _encoder { get; } = GetJsEncoder(); + private static readonly JavaScriptEncoder _encoder = GetJsEncoder(); - readonly IKvVaultStore _vault; - readonly INostrKeyEncoder _keyEncoder; - readonly INostrCryptoProvider _cryptoProvider; - - public NostrOpProvider(PluginBase plugin) - { - //Use base64 key encoder - _keyEncoder = new Base64KeyEncoder(); - - //Setup crypto provider - _cryptoProvider = plugin.CreateService<ManagedCryptoprovider>(); - - //Get the vault - _vault = plugin.CreateService<ManagedVaultClient>(); - } + private readonly IKvVaultStore _vault = plugin.CreateService<ManagedVaultClient>(); + private readonly INostrKeyEncoder _keyEncoder = new Base64KeyEncoder(); + private readonly INostrCryptoProvider _cryptoProvider = plugin.CreateService<ManagedCryptoprovider>(); ///<inheritdoc/> public Task<bool> CreateCredentialAsync(VaultUserScope scope, NostrKeyMeta newKey, CancellationToken cancellation) @@ -167,56 +155,30 @@ namespace NVault.Plugins.Vault private bool SignMessage(ReadOnlySpan<char> vaultKey, NostrEvent ev) { - //Decode the key - int keyBufSize = _keyEncoder.GetKeyBufferSize(vaultKey); - - //Get the signature buffer size - int sigBufSize = _cryptoProvider.GetSignatureBufferSize(); - - //Alloc key buffer - using IMemoryHandle<byte> buffHandle = MemoryUtil.SafeAllocNearestPage(keyBufSize + sigBufSize, true); - - //Wrap the buffer - EvBuffer buffer = new(buffHandle, keyBufSize, sigBufSize, (int)HashAlg.SHA256); + Span<byte> keyBuffer = stackalloc byte[_keyEncoder.GetKeyBufferSize(vaultKey)]; try { //Decode the key - ERRNO keySize = _keyEncoder.DecodeKey(vaultKey, buffer.KeyBuffer); + ERRNO keySize = _keyEncoder.DecodeKey(vaultKey, keyBuffer); if (!keySize) { return false; } - //Get the event id/event digest from the event - GetNostrEventId(ev, buffer.HashBuffer); - - //Store the event id - ev.Id = Convert.ToHexString(buffer.HashBuffer).ToLower(); - - //Sign the event - ERRNO sigSize = _cryptoProvider.SignMessage(buffer.KeyBuffer[..(int)keySize], buffer.HashBuffer, buffer.SigBuffer); - - if (!sigSize) - { - return false; - } - - //Store the signature as loewrcase hex - ev.Signature = Convert.ToHexString(buffer.SigBuffer[..(int)sigSize]).ToLower(); - return true; + //Compute the message signature + return ComputeMessageSignature(ev, keyBuffer[0.. (int)keySize]); } finally { - //Zero the key buffer and key - MemoryUtil.InitializeBlock(buffHandle.Span); + //Zero the key before returning + MemoryUtil.InitializeBlock(keyBuffer); } } - private void GetNostrEventId(NostrEvent evnt, Span<byte> idHash) + private bool ComputeMessageSignature(NostrEvent evnt, ReadOnlySpan<byte> secKey) { - JsonWriterOptions options = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, @@ -224,6 +186,9 @@ namespace NVault.Plugins.Vault MaxDepth = 4 }; + Span<byte> idHash = stackalloc byte[(int)HashAlg.SHA256]; + Span<byte> sigBuffer = stackalloc byte[_cryptoProvider.GetSignatureBufferSize()]; + using VnMemoryStream ms = new(); using (Utf8JsonWriter writer = new(ms, options)) { @@ -259,8 +224,23 @@ namespace NVault.Plugins.Vault //Compute the hash if (!ManagedHash.ComputeHash(ms.AsSpan(), idHash, HashAlg.SHA256)) { - throw new CryptographicException("Failed to compute event data hash"); + return false; + } + + //Set message id + evnt.Id = Convert.ToHexString(idHash).ToLower(); + + if(_cryptoProvider.SignData(secKey, ms.AsSpan(), sigBuffer) != sigBuffer.Length) + { + return false; } + + //Set the signature as lowercase hex + evnt.Signature = Convert.ToHexString(sigBuffer).ToLower(); + + MemoryUtil.InitializeBlock(sigBuffer); + + return true; } private static JavaScriptEncoder GetJsEncoder() @@ -442,14 +422,5 @@ namespace NVault.Plugins.Vault MemoryUtil.InitializeBlock(ivBuffer); } } - - readonly record struct EvBuffer(IMemoryHandle<byte> Handle, int KeySize, int SigSize, int HashSize) - { - public readonly Span<byte> KeyBuffer => Handle.Span[..KeySize]; - - public readonly Span<byte> SigBuffer => Handle.AsSpan(KeySize, SigSize); - - public readonly Span<byte> HashBuffer => Handle.AsSpan(KeySize + SigSize, HashSize); - } } } diff --git a/back-end/plugins/nvault/src/Properties/launchSettings.json b/back-end/plugins/nvault/src/Properties/launchSettings.json new file mode 100644 index 0000000..0f138be --- /dev/null +++ b/back-end/plugins/nvault/src/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "NVault": { + "commandName": "Project", + "nativeDebugging": true + } + } +}
\ No newline at end of file |