From a9eb6255f80b9a5aaaf6e2303103cb8ee7b08639 Mon Sep 17 00:00:00 2001 From: Li Date: Wed, 10 Jan 2024 16:13:05 +1000 Subject: [PATCH] Pull encryption operations into the thread pool --- LiftLog.Lib/Services/IEncryptionService.cs | 14 +- LiftLog.Lib/Services/OsEncryptionService.cs | 167 +++++++++++--------- LiftLog.Web/Services/JsEncryptionService.cs | 60 ++++--- 3 files changed, 124 insertions(+), 117 deletions(-) diff --git a/LiftLog.Lib/Services/IEncryptionService.cs b/LiftLog.Lib/Services/IEncryptionService.cs index 60471f07..7a1d0f8b 100644 --- a/LiftLog.Lib/Services/IEncryptionService.cs +++ b/LiftLog.Lib/Services/IEncryptionService.cs @@ -15,7 +15,7 @@ public interface IEncryptionService /// /// /// - ValueTask DecryptAesCbcAndVerifyRsa256PssAsync( + Task DecryptAesCbcAndVerifyRsa256PssAsync( AesEncryptedAndRsaSignedData data, AesKey key, RsaPublicKey publicKey @@ -26,7 +26,7 @@ RsaPublicKey publicKey /// CBC does not guarantee integrity, so we need to supply a signature with the data. /// The signature is verified on decryption. Note the signature is also encrypted. /// - public ValueTask SignRsa256PssAndEncryptAesCbcAsync( + public Task SignRsa256PssAndEncryptAesCbcAsync( byte[] data, AesKey key, RsaPrivateKey rsaPrivateKey, @@ -35,15 +35,9 @@ public ValueTask SignRsa256PssAndEncryptAesCbcAsyn ValueTask GenerateAesKeyAsync(); - public ValueTask DecryptRsaOaepSha256Async( - RsaEncryptedData data, - RsaPrivateKey privateKey - ); + public Task DecryptRsaOaepSha256Async(RsaEncryptedData data, RsaPrivateKey privateKey); - public ValueTask EncryptRsaOaepSha256Async( - byte[] data, - RsaPublicKey publicKey - ); + public Task EncryptRsaOaepSha256Async(byte[] data, RsaPublicKey publicKey); public ValueTask GenerateRsaKeysAsync(); } diff --git a/LiftLog.Lib/Services/OsEncryptionService.cs b/LiftLog.Lib/Services/OsEncryptionService.cs index b705fca5..a915d69a 100644 --- a/LiftLog.Lib/Services/OsEncryptionService.cs +++ b/LiftLog.Lib/Services/OsEncryptionService.cs @@ -2,81 +2,100 @@ namespace LiftLog.Lib.Services; +/// +/// This class provides encryption services using the OS's native crypto libraries. +/// It will run all encryption operations on the thread pool. +/// [System.Runtime.Versioning.UnsupportedOSPlatform("browser")] public class OsEncryptionService : IEncryptionService { - public ValueTask DecryptAesCbcAndVerifyRsa256PssAsync( + public Task DecryptAesCbcAndVerifyRsa256PssAsync( AesEncryptedAndRsaSignedData encryptedPayload, AesKey key, RsaPublicKey publicKey ) { - var aes = Aes.Create(); + return Task.Run(() => + { + var aes = Aes.Create(); - aes.IV = encryptedPayload.IV.Value; - aes.Key = key.Value; + aes.IV = encryptedPayload.IV.Value; + aes.Key = key.Value; - using var decryptor = aes.CreateDecryptor(); + using var decryptor = aes.CreateDecryptor(); - var decrypted = decryptor.TransformFinalBlock( - encryptedPayload.EncryptedPayload, - 0, - encryptedPayload.EncryptedPayload.Length - ); + var decrypted = decryptor.TransformFinalBlock( + encryptedPayload.EncryptedPayload, + 0, + encryptedPayload.EncryptedPayload.Length + ); - var data = decrypted[..^IEncryptionService.RsaHashLength]; - var signature = decrypted[^IEncryptionService.RsaHashLength..]; + var data = decrypted[..^IEncryptionService.RsaHashLength]; + var signature = decrypted[^IEncryptionService.RsaHashLength..]; - var rsa = RSA.Create(); - rsa.ImportSubjectPublicKeyInfo(publicKey.SpkiPublicKeyBytes, out _); - var dataHash = SHA256.HashData(data); + var rsa = RSA.Create(); + rsa.ImportSubjectPublicKeyInfo(publicKey.SpkiPublicKeyBytes, out _); + var dataHash = SHA256.HashData(data); - if (!rsa.VerifyData(dataHash, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss)) - { - throw new SignatureMismatchException("Signature verification failed"); - } + if ( + !rsa.VerifyData( + dataHash, + signature, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pss + ) + ) + { + throw new SignatureMismatchException("Signature verification failed"); + } - return ValueTask.FromResult(data); + return data; + }); } - public ValueTask SignRsa256PssAndEncryptAesCbcAsync( + public Task SignRsa256PssAndEncryptAesCbcAsync( byte[] data, AesKey key, RsaPrivateKey rsaPrivateKey, AesIV? iv = null ) { - var rsa = RSA.Create(2048); + return Task.Run(() => + { + var rsa = RSA.Create(2048); - rsa.ImportPkcs8PrivateKey(rsaPrivateKey.Pkcs8PrivateKeyBytes, out _); + rsa.ImportPkcs8PrivateKey(rsaPrivateKey.Pkcs8PrivateKeyBytes, out _); - var dataHash = SHA256.HashData(data); - var signature = rsa.SignData(dataHash, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + var dataHash = SHA256.HashData(data); + var signature = rsa.SignData( + dataHash, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pss + ); - var dataAndSignature = new byte[data.Length + signature.Length]; - data.CopyTo(dataAndSignature, 0); - signature.CopyTo(dataAndSignature, data.Length); + var dataAndSignature = new byte[data.Length + signature.Length]; + data.CopyTo(dataAndSignature, 0); + signature.CopyTo(dataAndSignature, data.Length); - var aes = Aes.Create(); + var aes = Aes.Create(); - if (iv is not null) - { - aes.IV = iv.Value; - } - else - { - aes.GenerateIV(); - } - aes.Key = key.Value; + if (iv is not null) + { + aes.IV = iv.Value; + } + else + { + aes.GenerateIV(); + } + aes.Key = key.Value; - using var encryptor = aes.CreateEncryptor(); + using var encryptor = aes.CreateEncryptor(); - return ValueTask.FromResult( - new AesEncryptedAndRsaSignedData( + return new AesEncryptedAndRsaSignedData( encryptor.TransformFinalBlock(dataAndSignature, 0, dataAndSignature.Length), new AesIV(iv?.Value ?? aes.IV) - ) - ); + ); + }); } public ValueTask GenerateAesKeyAsync() @@ -88,46 +107,46 @@ public ValueTask GenerateAesKeyAsync() return ValueTask.FromResult(new AesKey(aes.Key)); } - public ValueTask EncryptRsaOaepSha256Async( - byte[] data, - RsaPublicKey publicKey - ) + public Task EncryptRsaOaepSha256Async(byte[] data, RsaPublicKey publicKey) { - var rsa = RSA.Create(2048); - - rsa.ImportSubjectPublicKeyInfo(publicKey.SpkiPublicKeyBytes, out _); - - // encrypt one chunk at a time, as RSA has a size limit of 122ish bytes - var encryptedChunks = new byte[(data.Length / 122) + 1][]; - for (var i = 0; i < encryptedChunks.Length; i++) + return Task.Run(() => { - encryptedChunks[i] = rsa.Encrypt( - data[(i * 122)..Math.Min((i + 1) * 122, data.Length)], - RSAEncryptionPadding.OaepSHA256 - ); - } - - return ValueTask.FromResult(new RsaEncryptedData(encryptedChunks)); + var rsa = RSA.Create(2048); + + rsa.ImportSubjectPublicKeyInfo(publicKey.SpkiPublicKeyBytes, out _); + + // encrypt one chunk at a time, as RSA has a size limit of 122ish bytes + var encryptedChunks = new byte[(data.Length / 122) + 1][]; + for (var i = 0; i < encryptedChunks.Length; i++) + { + encryptedChunks[i] = rsa.Encrypt( + data[(i * 122)..Math.Min((i + 1) * 122, data.Length)], + RSAEncryptionPadding.OaepSHA256 + ); + } + + return new RsaEncryptedData(encryptedChunks); + }); } - public ValueTask DecryptRsaOaepSha256Async( - RsaEncryptedData data, - RsaPrivateKey privateKey - ) + public Task DecryptRsaOaepSha256Async(RsaEncryptedData data, RsaPrivateKey privateKey) { - var rsa = RSA.Create(); + return Task.Run(() => + { + var rsa = RSA.Create(); - rsa.ImportPkcs8PrivateKey(privateKey.Pkcs8PrivateKeyBytes, out _); + rsa.ImportPkcs8PrivateKey(privateKey.Pkcs8PrivateKeyBytes, out _); - // decrypt one chunk at a time + // decrypt one chunk at a time - var decrypted = new byte[data.DataChunks.Length][]; - for (var i = 0; i < data.DataChunks.Length; i++) - { - decrypted[i] = rsa.Decrypt(data.DataChunks[i], RSAEncryptionPadding.OaepSHA256); - } + var decrypted = new byte[data.DataChunks.Length][]; + for (var i = 0; i < data.DataChunks.Length; i++) + { + decrypted[i] = rsa.Decrypt(data.DataChunks[i], RSAEncryptionPadding.OaepSHA256); + } - return ValueTask.FromResult(decrypted.SelectMany(x => x).ToArray()); + return decrypted.SelectMany(x => x).ToArray(); + }); } public ValueTask GenerateRsaKeysAsync() diff --git a/LiftLog.Web/Services/JsEncryptionService.cs b/LiftLog.Web/Services/JsEncryptionService.cs index c60badeb..1d9df8ea 100644 --- a/LiftLog.Web/Services/JsEncryptionService.cs +++ b/LiftLog.Web/Services/JsEncryptionService.cs @@ -7,34 +7,38 @@ namespace LiftLog.Web.Services; // Dotnet AES-GCM implementation is not compatible with the JS implementation public class JsEncryptionService(IJSRuntime jSRuntime) : IEncryptionService { - public ValueTask DecryptAesCbcAndVerifyRsa256PssAsync( + public Task DecryptAesCbcAndVerifyRsa256PssAsync( AesEncryptedAndRsaSignedData data, AesKey key, RsaPublicKey publicKey ) { - return jSRuntime.InvokeAsync( - "CryptoUtils.decryptAesCbcAndVerifyRsa256PssAsync", - data, - key, - publicKey - ); + return jSRuntime + .InvokeAsync( + "CryptoUtils.decryptAesCbcAndVerifyRsa256PssAsync", + data, + key, + publicKey + ) + .AsTask(); } - public ValueTask SignRsa256PssAndEncryptAesCbcAsync( + public Task SignRsa256PssAndEncryptAesCbcAsync( byte[] data, AesKey key, RsaPrivateKey rsaPrivateKey, AesIV? iv = null ) { - return jSRuntime.InvokeAsync( - "CryptoUtils.signRsa256PssAndEncryptAesCbcAsync", - data, - key, - rsaPrivateKey, - iv - ); + return jSRuntime + .InvokeAsync( + "CryptoUtils.signRsa256PssAndEncryptAesCbcAsync", + data, + key, + rsaPrivateKey, + iv + ) + .AsTask(); } public ValueTask GenerateAesKeyAsync() @@ -42,28 +46,18 @@ public ValueTask GenerateAesKeyAsync() return jSRuntime.InvokeAsync("CryptoUtils.generateAesKey"); } - public ValueTask EncryptRsaOaepSha256Async( - byte[] data, - RsaPublicKey publicKey - ) + public Task EncryptRsaOaepSha256Async(byte[] data, RsaPublicKey publicKey) { - return jSRuntime.InvokeAsync( - "CryptoUtils.encryptRsaOaepSha256Async", - data, - publicKey - ); + return jSRuntime + .InvokeAsync("CryptoUtils.encryptRsaOaepSha256Async", data, publicKey) + .AsTask(); } - public ValueTask DecryptRsaOaepSha256Async( - RsaEncryptedData data, - RsaPrivateKey privateKey - ) + public Task DecryptRsaOaepSha256Async(RsaEncryptedData data, RsaPrivateKey privateKey) { - return jSRuntime.InvokeAsync( - "CryptoUtils.decryptRsaOaepSha256Async", - data, - privateKey - ); + return jSRuntime + .InvokeAsync("CryptoUtils.decryptRsaOaepSha256Async", data, privateKey) + .AsTask(); } public ValueTask GenerateRsaKeysAsync()