From 8434d8b8ecf54c6038a7e9ad7d57084a0865500c Mon Sep 17 00:00:00 2001 From: vnugent Date: Thu, 23 Nov 2023 12:44:53 -0500 Subject: fixed signing and working note encryption! --- README.md | 7 +- .../src/ContextExtensions.cs | 23 +++- .../NVault.Crypto.Secp256k1/src/LibSecp256k1.cs | 5 + back-end/plugins/nvault/src/Endpoints/Endpoint.cs | 6 +- .../plugins/nvault/src/NativeSecp256k1Library.cs | 9 +- back-end/plugins/nvault/src/NostrOpProvider.cs | 24 ++-- .../primary/components/PromptPopup.vue | 24 ++-- .../src/entries/contentScript/primary/main.js | 4 +- .../src/entries/contentScript/primary/style.scss | 11 +- extension/src/entries/contentScript/util.ts | 96 +++++++++------ extension/src/entries/nostr-provider.js | 132 +++++++++------------ extension/src/features/nostr-api.ts | 2 +- extension/src/features/server-api/index.ts | 5 +- 13 files changed, 201 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index 3d47e28..b6367ff 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,11 @@ This project is probably best explained by the features it has an that need to b - ✔ SSL and all the basic web security - ✔ Multi user support for friends and family - ✔ Support loading external random library (native or managed dll) -- Note encryption/decryption (in progress) +- ✔ Note encryption/decryption - Support a connected, or network based signing hardware - Optionally support network based, event authorization applications - Server backed event history to preserve your notes +- Support for NIP-46 event signing using an extern library ### Extension - ✔ Infinite identities per account @@ -30,11 +31,11 @@ This project is probably best explained by the features it has an that need to b - ✔ Easy identity selection - ✔ Per user NIP-05 identity export - ✔ Dark/light theme +- ✔ NIP-07 encryption - Preferred relay storage (also NIP-05 relays) -- NIP-07 encryption (in progress) - Fine grained event permissions - Event history -- A good looking UI +- A good looking UI (in progress) - Chrome and Firefox support (mobile would be nice also) - Build fully featured library/API for other extension builders - Stip metadata tags in events such as [#7f57800e](https://github.com/nostr-protocol/nips/pull/884/commits/7f27800e27c437ce17d223799f37631105d1ae5f) diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs b/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs index f5327df..9931698 100644 --- a/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs +++ b/back-end/libs/NVault.Crypto.Secp256k1/src/ContextExtensions.cs @@ -254,7 +254,7 @@ namespace NVault.Crypto.Secp256k1 { if (secretKey.Length != SecretKeySize) { - throw new ArgumentException($"Your public key buffer must be exactly {SecretKeySize} bytes long"); + throw new ArgumentException($"Your secret key buffer must be exactly {SecretKeySize} bytes long"); } //Init callback state struct @@ -265,17 +265,28 @@ namespace NVault.Crypto.Secp256k1 OutLen = data.Length }; + context.Lib.SafeLibHandle.ThrowIfClosed(); + //Stack allocated keypair and x-only public key - Secp256k1PublicKey pubKeyStruct = new(); - //Recover the x-only public key structure - MemoryUtil.CopyStruct(xOnlyPubKey, &pubKeyStruct); + Secp256k1PublicKey peerPubKey = new(); - context.Lib.SafeLibHandle.ThrowIfClosed(); + //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, &pubKeyStruct, secKeyPtr, UmanagedEcdhHashFuncCallback, &state) == 1; + return context.Lib._ecdh.Invoke( + context.Context, + dataPtr, + &peerPubKey, + secKeyPtr, + UmanagedEcdhHashFuncCallback, + &state + ) == 1; } /* diff --git a/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs b/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs index 5aeed00..f3afc33 100644 --- a/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs +++ b/back-end/libs/NVault.Crypto.Secp256k1/src/LibSecp256k1.cs @@ -82,6 +82,9 @@ namespace NVault.Crypto.Secp256k1 [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, @@ -143,6 +146,7 @@ namespace NVault.Crypto.Secp256k1 internal readonly SecKeyVerify _secKeyVerify; internal readonly PubKeySerialize _pubKeySerialize; internal readonly Ecdh _ecdh; + internal readonly XOnlyPubkeyParse _xOnlyPubkeyParse; private readonly IRandomSource _randomSource; /// @@ -171,6 +175,7 @@ namespace NVault.Crypto.Secp256k1 _secKeyVerify = handle.DangerousGetMethod(); _pubKeySerialize = handle.DangerousGetMethod(); _ecdh = handle.DangerousGetMethod(); + _xOnlyPubkeyParse = handle.DangerousGetMethod(); //Store random source _randomSource = randomSource; diff --git a/back-end/plugins/nvault/src/Endpoints/Endpoint.cs b/back-end/plugins/nvault/src/Endpoints/Endpoint.cs index f718c2f..bcebc63 100644 --- a/back-end/plugins/nvault/src/Endpoints/Endpoint.cs +++ b/back-end/plugins/nvault/src/Endpoints/Endpoint.cs @@ -517,9 +517,11 @@ namespace NVault.Plugins.Vault.Endpoints .NotEmpty() .Length(0, 10000) //Make sure iv exists - .Must(ct => ct.Contains("iv?=", StringComparison.OrdinalIgnoreCase)) + .Must(ct => ct.Contains("?iv=", StringComparison.OrdinalIgnoreCase)) + .WithMessage("iv not found in ciphertext") //Check iv is not too long - .Must(ct => ct.AsSpan().SliceAfterParam("iv?=").Length < 28); + .Must(ct => ct.AsSpan().SliceAfterParam("?iv=").Length == NostrOpProvider.MaxBase64EncodedSize) + .WithMessage("iv is not the correct size"); //Pubpkey must be 64 hex characters validationRules.RuleFor(p => p.OtherPubKey) diff --git a/back-end/plugins/nvault/src/NativeSecp256k1Library.cs b/back-end/plugins/nvault/src/NativeSecp256k1Library.cs index 55cf2de..0870156 100644 --- a/back-end/plugins/nvault/src/NativeSecp256k1Library.cs +++ b/back-end/plugins/nvault/src/NativeSecp256k1Library.cs @@ -71,7 +71,7 @@ namespace NVault.Plugins.Vault aes.Key = sharedKeyBuffer; aes.Mode = CipherMode.CBC; - return aes.DecryptCbc(ciphterText, aesIv, outputBuffer, PaddingMode.None); + return aes.DecryptCbc(ciphterText, aesIv, outputBuffer, PaddingMode.Zeros); } finally { @@ -99,14 +99,17 @@ namespace NVault.Plugins.Vault try { //Get the Secp256k1 shared key - context.ComputeSharedKey(sharedKeyBuffer, targetKey, secretKey, HashFuncCallback, IntPtr.Zero); + 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.None); + return aes.EncryptCbc(plainText, aesIv, cipherText, PaddingMode.Zeros); } finally { diff --git a/back-end/plugins/nvault/src/NostrOpProvider.cs b/back-end/plugins/nvault/src/NostrOpProvider.cs index eed64b7..aa4840e 100644 --- a/back-end/plugins/nvault/src/NostrOpProvider.cs +++ b/back-end/plugins/nvault/src/NostrOpProvider.cs @@ -34,12 +34,12 @@ using VNLib.Plugins.Extensions.Loading; using NVault.Plugins.Vault.Model; - namespace NVault.Plugins.Vault { internal sealed class NostrOpProvider : INostrOperations { - const int NIP04_RANDOM_IV_SIZE = 16; + public const int AES_IV_SIZE = 16; + public static int MaxBase64EncodedSize { get; } = Base64.GetMaxEncodedToUtf8Length(AES_IV_SIZE); private static JavaScriptEncoder _encoder { get; } = GetJsEncoder(); @@ -350,7 +350,7 @@ namespace NVault.Plugins.Vault //Small buffers for private key and raw iv Span privKeyBytes = stackalloc byte[keyBufSize]; - Span ivBuffer = stackalloc byte[encipher ? NIP04_RANDOM_IV_SIZE : 64]; + Span ivBuffer = stackalloc byte[encipher ? AES_IV_SIZE : 64]; try { @@ -391,14 +391,15 @@ namespace NVault.Plugins.Vault ReadOnlySpan cipherText = text.SliceBeforeParam("?iv="); ReadOnlySpan ivSegment = text.SliceAfterParam("?iv="); - if (ivSegment.Length > 128) + if (ivSegment.Length > MaxBase64EncodedSize) { throw new ArgumentException("initialization vector is larger than allowed"); } //Decode initialziation vector ERRNO ivSize= VnEncoding.TryFromBase64Chars(ivSegment, ivBuffer); - if (ivSize < 1) + //Must be exactly the size of the AES block s + if (ivSize != AES_IV_SIZE) { return false; } @@ -412,7 +413,7 @@ namespace NVault.Plugins.Vault //Decrypt the message ERRNO outputSize = _cryptoProvider.DecryptMessage( - privKeyBytes, + privKeyBytes[..(int)keySize], pubKey, ivBuffer.Slice(0, ivSize), ctBuffer.AsSpan(0, ctSize), @@ -424,6 +425,13 @@ namespace NVault.Plugins.Vault return false; } + Span output = outputBuffer.Span; + //trim trailing zeros + while (outputSize > 0 && output[outputSize - 1] == 0) + { + outputSize--; + } + //Store the output text (deciphered text) outputText = Encoding.UTF8.GetString(outputBuffer.AsSpan(0, outputSize)); @@ -433,8 +441,8 @@ namespace NVault.Plugins.Vault finally { //Zero the key buffer and key - MemoryUtil.InitializeBlock(ctBuffer.Span); - MemoryUtil.InitializeBlock(outputBuffer.Span); + MemoryUtil.InitializeBlock(ref ctBuffer.GetReference(), outputBuffer.IntLength); + MemoryUtil.InitializeBlock(ref outputBuffer.GetReference(), outputBuffer.IntLength); MemoryUtil.InitializeBlock(privKeyBytes); MemoryUtil.InitializeBlock(ivBuffer); } diff --git a/extension/src/entries/contentScript/primary/components/PromptPopup.vue b/extension/src/entries/contentScript/primary/components/PromptPopup.vue index 381f7b3..d019b5d 100644 --- a/extension/src/entries/contentScript/primary/components/PromptPopup.vue +++ b/extension/src/entries/contentScript/primary/components/PromptPopup.vue @@ -1,12 +1,14 @@