diff --git a/apps/server/AliasVault.Client/Services/EmailService.cs b/apps/server/AliasVault.Client/Services/EmailService.cs
index b50d5d898..2d66432ee 100644
--- a/apps/server/AliasVault.Client/Services/EmailService.cs
+++ b/apps/server/AliasVault.Client/Services/EmailService.cs
@@ -80,12 +80,12 @@ public sealed class EmailService(DbService dbService, JsInteropService jsInterop
try
{
- var decryptedSymmetricKey = await jsInteropService.DecryptWithPrivateKey(email.EncryptedSymmetricKey, privateKey.PrivateKey);
- email.Subject = await jsInteropService.SymmetricDecrypt(email.Subject, Convert.ToBase64String(decryptedSymmetricKey));
- email.FromDisplay = await jsInteropService.SymmetricDecrypt(email.FromDisplay, Convert.ToBase64String(decryptedSymmetricKey));
- email.FromLocal = await jsInteropService.SymmetricDecrypt(email.FromLocal, Convert.ToBase64String(decryptedSymmetricKey));
- email.FromDomain = await jsInteropService.SymmetricDecrypt(email.FromDomain, Convert.ToBase64String(decryptedSymmetricKey));
- email.MessagePreview = await jsInteropService.SymmetricDecrypt(email.MessagePreview, Convert.ToBase64String(decryptedSymmetricKey));
+ var decryptedSymmetricKeyBase64 = await jsInteropService.DecryptWithPrivateKey(email.EncryptedSymmetricKey, privateKey.PrivateKey);
+ email.Subject = await jsInteropService.SymmetricDecrypt(email.Subject, decryptedSymmetricKeyBase64);
+ email.FromDisplay = await jsInteropService.SymmetricDecrypt(email.FromDisplay, decryptedSymmetricKeyBase64);
+ email.FromLocal = await jsInteropService.SymmetricDecrypt(email.FromLocal, decryptedSymmetricKeyBase64);
+ email.FromDomain = await jsInteropService.SymmetricDecrypt(email.FromDomain, decryptedSymmetricKeyBase64);
+ email.MessagePreview = await jsInteropService.SymmetricDecrypt(email.MessagePreview, decryptedSymmetricKeyBase64);
}
catch (Exception ex)
{
@@ -110,8 +110,8 @@ public sealed class EmailService(DbService dbService, JsInteropService jsInterop
try
{
- var decryptedSymmetricKey = await jsInteropService.DecryptWithPrivateKey(email.EncryptedSymmetricKey, privateKey.PrivateKey);
- var decryptedBase64 = await jsInteropService.SymmetricDecryptBytes(encryptedBytes, Convert.ToBase64String(decryptedSymmetricKey));
+ var decryptedSymmetricKeyBase64 = await jsInteropService.DecryptWithPrivateKey(email.EncryptedSymmetricKey, privateKey.PrivateKey);
+ var decryptedBase64 = await jsInteropService.SymmetricDecryptBytes(encryptedBytes, decryptedSymmetricKeyBase64);
return decryptedBase64;
}
catch (Exception ex)
@@ -133,21 +133,21 @@ public sealed class EmailService(DbService dbService, JsInteropService jsInterop
try
{
- var decryptedSymmetricKey = await jsInteropService.DecryptWithPrivateKey(email.EncryptedSymmetricKey, privateKey.PrivateKey);
- email.Subject = await jsInteropService.SymmetricDecrypt(email.Subject, Convert.ToBase64String(decryptedSymmetricKey));
+ var decryptedSymmetricKeyBase64 = await jsInteropService.DecryptWithPrivateKey(email.EncryptedSymmetricKey, privateKey.PrivateKey);
+ email.Subject = await jsInteropService.SymmetricDecrypt(email.Subject, decryptedSymmetricKeyBase64);
if (email.MessageHtml is not null)
{
- email.MessageHtml = await jsInteropService.SymmetricDecrypt(email.MessageHtml, Convert.ToBase64String(decryptedSymmetricKey));
+ email.MessageHtml = await jsInteropService.SymmetricDecrypt(email.MessageHtml, decryptedSymmetricKeyBase64);
}
if (email.MessagePlain is not null)
{
- email.MessagePlain = await jsInteropService.SymmetricDecrypt(email.MessagePlain, Convert.ToBase64String(decryptedSymmetricKey));
+ email.MessagePlain = await jsInteropService.SymmetricDecrypt(email.MessagePlain, decryptedSymmetricKeyBase64);
}
- email.FromDisplay = await jsInteropService.SymmetricDecrypt(email.FromDisplay, Convert.ToBase64String(decryptedSymmetricKey));
- email.FromLocal = await jsInteropService.SymmetricDecrypt(email.FromLocal, Convert.ToBase64String(decryptedSymmetricKey));
- email.FromDomain = await jsInteropService.SymmetricDecrypt(email.FromDomain, Convert.ToBase64String(decryptedSymmetricKey));
+ email.FromDisplay = await jsInteropService.SymmetricDecrypt(email.FromDisplay, decryptedSymmetricKeyBase64);
+ email.FromLocal = await jsInteropService.SymmetricDecrypt(email.FromLocal, decryptedSymmetricKeyBase64);
+ email.FromDomain = await jsInteropService.SymmetricDecrypt(email.FromDomain, decryptedSymmetricKeyBase64);
}
catch (Exception ex)
{
diff --git a/apps/server/AliasVault.Client/Services/JsInterop/JsInteropService.cs b/apps/server/AliasVault.Client/Services/JsInterop/JsInteropService.cs
index 0f12323f3..4fc60280b 100644
--- a/apps/server/AliasVault.Client/Services/JsInterop/JsInteropService.cs
+++ b/apps/server/AliasVault.Client/Services/JsInterop/JsInteropService.cs
@@ -199,13 +199,13 @@ public sealed class JsInteropService(IJSRuntime jsRuntime)
///
/// Ciphertext to decrypt.
/// Private key to use for decryption.
- /// Decrypted string.
- public async Task DecryptWithPrivateKey(string base64Ciphertext, string privateKey)
+ /// Decrypted bytes as base64 string.
+ public async Task DecryptWithPrivateKey(string base64Ciphertext, string privateKey)
{
try
{
- // Invoke the JavaScript function and get the result as a byte array
- byte[] result = await jsRuntime.InvokeAsync("rsaInterop.decryptWithPrivateKey", base64Ciphertext, privateKey);
+ // Invoke the JavaScript function and get the result as a base64 string
+ string result = await jsRuntime.InvokeAsync("rsaInterop.decryptWithPrivateKey", base64Ciphertext, privateKey);
return result;
}
catch (JSException ex)
diff --git a/apps/server/AliasVault.Client/wwwroot/js/crypto.js b/apps/server/AliasVault.Client/wwwroot/js/crypto.js
index 7610ba8af..ecdd21190 100644
--- a/apps/server/AliasVault.Client/wwwroot/js/crypto.js
+++ b/apps/server/AliasVault.Client/wwwroot/js/crypto.js
@@ -28,7 +28,7 @@ function checkCryptoAvailable() {
window.cryptoInterop = {
encrypt: async function (plaintext, base64Key) {
checkCryptoAvailable();
-
+
const key = await window.crypto.subtle.importKey(
"raw",
Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)),
@@ -62,7 +62,7 @@ window.cryptoInterop = {
},
decrypt: async function (base64Ciphertext, base64Key) {
checkCryptoAvailable();
-
+
const key = await window.crypto.subtle.importKey(
"raw",
Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)),
@@ -89,7 +89,7 @@ window.cryptoInterop = {
},
decryptBytes: async function (base64Ciphertext, base64Key) {
checkCryptoAvailable();
-
+
const key = await window.crypto.subtle.importKey(
"raw",
Uint8Array.from(atob(base64Key), c => c.charCodeAt(0)),
@@ -126,7 +126,7 @@ window.rsaInterop = {
*/
generateRsaKeyPair : async function() {
checkCryptoAvailable();
-
+
const keyPair = await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
@@ -154,7 +154,7 @@ window.rsaInterop = {
*/
encryptWithPublicKey : async function(plaintext, publicKey) {
checkCryptoAvailable();
-
+
const publicKeyObj = await window.crypto.subtle.importKey(
"jwk",
JSON.parse(publicKey),
@@ -181,11 +181,11 @@ window.rsaInterop = {
* Decrypts a ciphertext string using an RSA private key.
* @param {string} ciphertext - The base64-encoded ciphertext to decrypt.
* @param {string} privateKey - The private key in JWK format.
- * @returns {Promise} A promise that resolves to the decrypted data as a Uint8Array.
+ * @returns {Promise} A promise that resolves to the decrypted data as a base64 string.
*/
decryptWithPrivateKey: async function(ciphertext, privateKey) {
checkCryptoAvailable();
-
+
try {
// Parse the private key
let parsedPrivateKey = JSON.parse(privateKey);
@@ -215,8 +215,9 @@ window.rsaInterop = {
cipherBuffer
);
- // Return the decrypted data as a Uint8Array
- return new Uint8Array(plaintextBuffer);
+ // Convert to base64 string instead of returning Uint8Array to avoid Blazor serialization issues, see https://github.com/dotnet/aspnetcore/issues/59837
+ const decryptedBytes = new Uint8Array(plaintextBuffer);
+ return btoa(String.fromCharCode.apply(null, Array.from(decryptedBytes)));
} catch (error) {
throw new Error(`Failed to decrypt: ${error.message}`);
}