From 50da09f023b2095ad405a5f589e51d5d5bbe0ea4 Mon Sep 17 00:00:00 2001 From: Li Date: Wed, 21 Aug 2024 16:41:48 +1000 Subject: [PATCH 1/3] Add RSA verification that message came from the user who it relates to --- LiftLog.Api/LiftLog.Api.csproj | 2 + .../Service/PurchaseVerificationService.cs | 1 - LiftLog.Lib/Services/IEncryptionService.cs | 8 ++++ LiftLog.Maui/LiftLog.Maui.csproj | 2 + .../Services/AppHapticFeedbackService.cs | 1 - LiftLog.Maui/Services/AppThemeProvider.cs | 10 ----- LiftLog.Ui/LiftLog.Ui.csproj | 2 + LiftLog.Ui/Models/FeedStateDao.cs | 11 +++++ LiftLog.Ui/Models/UserEvent.proto | 2 + LiftLog.Ui/Services/FeedFollowService.cs | 18 ++++++++ .../Shared/Smart/SessionComponent.razor | 1 - .../CurrentSession/CurrentSessionEffects.cs | 3 -- .../CurrentSession/CurrentSessionReducers.cs | 3 -- .../Store/Feed/FeedEffects.Following.cs | 43 ++++++++++++++++++- LiftLog.Web/LiftLog.Web.csproj | 2 + LiftLog.Web/Services/WebThemeProvider.cs | 5 --- 16 files changed, 89 insertions(+), 25 deletions(-) diff --git a/LiftLog.Api/LiftLog.Api.csproj b/LiftLog.Api/LiftLog.Api.csproj index baa4c32c..71aea8e9 100644 --- a/LiftLog.Api/LiftLog.Api.csproj +++ b/LiftLog.Api/LiftLog.Api.csproj @@ -7,6 +7,8 @@ true true liftlog-api + + CS8524 diff --git a/LiftLog.Api/Service/PurchaseVerificationService.cs b/LiftLog.Api/Service/PurchaseVerificationService.cs index 1ab186bf..a527d634 100644 --- a/LiftLog.Api/Service/PurchaseVerificationService.cs +++ b/LiftLog.Api/Service/PurchaseVerificationService.cs @@ -17,7 +17,6 @@ public Task IsValidPurchaseToken(AppStore appStore, string proToken) proToken ), AppStore.Web => webAuthPurchaseVerificationService.IsValidPurchaseToken(proToken), - _ => throw new NotSupportedException(), }; } } diff --git a/LiftLog.Lib/Services/IEncryptionService.cs b/LiftLog.Lib/Services/IEncryptionService.cs index 7a1d0f8b..ceeae554 100644 --- a/LiftLog.Lib/Services/IEncryptionService.cs +++ b/LiftLog.Lib/Services/IEncryptionService.cs @@ -40,6 +40,14 @@ public Task SignRsa256PssAndEncryptAesCbcAsync( public Task EncryptRsaOaepSha256Async(byte[] data, RsaPublicKey publicKey); public ValueTask GenerateRsaKeysAsync(); + + public Task SignRsaPssSha256Async(byte[] data, RsaPrivateKey privateKey); + + public Task VerifyRsaPssSha256Async( + byte[] data, + byte[] signature, + RsaPublicKey publicKey + ); } public record RsaPublicKey(byte[] SpkiPublicKeyBytes); diff --git a/LiftLog.Maui/LiftLog.Maui.csproj b/LiftLog.Maui/LiftLog.Maui.csproj index 23be2e13..ad8999db 100644 --- a/LiftLog.Maui/LiftLog.Maui.csproj +++ b/LiftLog.Maui/LiftLog.Maui.csproj @@ -11,6 +11,8 @@ true enable false + + CS8524 LiftLog diff --git a/LiftLog.Maui/Services/AppHapticFeedbackService.cs b/LiftLog.Maui/Services/AppHapticFeedbackService.cs index f90ab5cb..dc3af275 100644 --- a/LiftLog.Maui/Services/AppHapticFeedbackService.cs +++ b/LiftLog.Maui/Services/AppHapticFeedbackService.cs @@ -11,7 +11,6 @@ public Task PerformAsync(Ui.Services.HapticFeedbackType amount) { Ui.Services.HapticFeedbackType.Click => HapticFeedbackType.Click, Ui.Services.HapticFeedbackType.LongPress => HapticFeedbackType.LongPress, - _ => throw new ArgumentOutOfRangeException(nameof(amount), amount, null), }; hapticFeedback.Perform(mapped); return Task.CompletedTask; diff --git a/LiftLog.Maui/Services/AppThemeProvider.cs b/LiftLog.Maui/Services/AppThemeProvider.cs index 8d949409..d65f85d3 100644 --- a/LiftLog.Maui/Services/AppThemeProvider.cs +++ b/LiftLog.Maui/Services/AppThemeProvider.cs @@ -21,11 +21,6 @@ public Task SetSeedColor(uint? seed, ThemePreference themePreference) ThemePreference.FollowSystem => AppTheme.Unspecified, ThemePreference.Light => AppTheme.Light, ThemePreference.Dark => AppTheme.Dark, - _ => throw new ArgumentOutOfRangeException( - nameof(themePreference), - themePreference, - null - ), }; if (seed is not null) { @@ -55,11 +50,6 @@ public ThemePreference GetThemePreference() AppTheme.Unspecified => ThemePreference.FollowSystem, AppTheme.Light => ThemePreference.Light, AppTheme.Dark => ThemePreference.Dark, - _ => throw new ArgumentOutOfRangeException( - nameof(Microsoft.Maui.Controls.Application.Current.UserAppTheme), - Microsoft.Maui.Controls.Application.Current.UserAppTheme, - null - ), }; } diff --git a/LiftLog.Ui/LiftLog.Ui.csproj b/LiftLog.Ui/LiftLog.Ui.csproj index 52fe4a06..ebbb5934 100644 --- a/LiftLog.Ui/LiftLog.Ui.csproj +++ b/LiftLog.Ui/LiftLog.Ui.csproj @@ -6,6 +6,8 @@ enable true true + + CS8524 diff --git a/LiftLog.Ui/Models/FeedStateDao.cs b/LiftLog.Ui/Models/FeedStateDao.cs index 6d04e00d..e2d02960 100644 --- a/LiftLog.Ui/Models/FeedStateDao.cs +++ b/LiftLog.Ui/Models/FeedStateDao.cs @@ -216,4 +216,15 @@ value is null FromUserId = value.UserId, FollowRequest = new FollowRequestDao { Name = value.Name }, }; + + public byte[] GetPayloadBytes() + { + return MessagePayloadCase switch + { + MessagePayloadOneofCase.FollowRequest => FollowRequest.ToByteArray(), + MessagePayloadOneofCase.FollowResponse => FollowResponse.ToByteArray(), + MessagePayloadOneofCase.UnfollowNotification => UnfollowNotification.ToByteArray(), + MessagePayloadOneofCase.None => [], + }; + } } diff --git a/LiftLog.Ui/Models/UserEvent.proto b/LiftLog.Ui/Models/UserEvent.proto index f124296e..197f259d 100644 --- a/LiftLog.Ui/Models/UserEvent.proto +++ b/LiftLog.Ui/Models/UserEvent.proto @@ -29,6 +29,8 @@ message InboxMessageDao { FollowResponseDao follow_response = 3; UnFollowNotification unfollow_notification = 4; } + // The signature of the [message + the to_user_id], signed with the private key of the from_user_id + bytes signature = 5; } message FollowRequestDao { diff --git a/LiftLog.Ui/Services/FeedFollowService.cs b/LiftLog.Ui/Services/FeedFollowService.cs index 94dd08d8..125b6523 100644 --- a/LiftLog.Ui/Services/FeedFollowService.cs +++ b/LiftLog.Ui/Services/FeedFollowService.cs @@ -20,6 +20,12 @@ public async Task RequestToFollowAUserAsync(FeedIdentity identity, Fe FromUserId = identity.Id, FollowRequest = new FollowRequestDao { Name = identity.Name }, }; + inboxMessage.Signature = ByteString.CopyFrom( + await encryptionService.SignRsaPssSha256Async( + [.. inboxMessage.GetPayloadBytes(), .. toFollow.Id.ToByteArray()], + identity.RsaKeyPair.PrivateKey + ) + ); var response = await feedApiService.PutInboxMessageAsync( new PutInboxMessageRequest( ToUserId: toFollow.Id, @@ -65,6 +71,12 @@ RsaPublicKey userPublicKey }, }, }; + inboxMessage.Signature = ByteString.CopyFrom( + await encryptionService.SignRsaPssSha256Async( + [.. inboxMessage.GetPayloadBytes(), .. request.UserId.ToByteArray()], + identity.RsaKeyPair.PrivateKey + ) + ); var encryptedMessage = await encryptionService.EncryptRsaOaepSha256Async( inboxMessage.ToByteArray(), userPublicKey @@ -95,6 +107,12 @@ RsaPublicKey userPublicKey FromUserId = identity.Id, FollowResponse = new FollowResponseDao { Rejected = new FollowResponseRejectedDao() }, }; + inboxMessage.Signature = ByteString.CopyFrom( + await encryptionService.SignRsaPssSha256Async( + [.. inboxMessage.GetPayloadBytes(), .. request.UserId.ToByteArray()], + identity.RsaKeyPair.PrivateKey + ) + ); RsaEncryptedData? encryptedMessage; try { diff --git a/LiftLog.Ui/Shared/Smart/SessionComponent.razor b/LiftLog.Ui/Shared/Smart/SessionComponent.razor index 18742d95..6ff3e670 100644 --- a/LiftLog.Ui/Shared/Smart/SessionComponent.razor +++ b/LiftLog.Ui/Shared/Smart/SessionComponent.razor @@ -173,7 +173,6 @@ else SessionTarget.WorkoutSession => CurrentSessionState.Value.WorkoutSession, SessionTarget.HistorySession => CurrentSessionState.Value.HistorySession, SessionTarget.FeedSession => CurrentSessionState.Value.FeedSession, - _ => throw new InvalidOperationException("Invalid session target") } ?? Session.Empty; [EditorRequired] [Parameter] public ImmutableDictionary> PreviouslyCompleted { get; set; } = null!; diff --git a/LiftLog.Ui/Store/CurrentSession/CurrentSessionEffects.cs b/LiftLog.Ui/Store/CurrentSession/CurrentSessionEffects.cs index 991cbf13..88e96132 100644 --- a/LiftLog.Ui/Store/CurrentSession/CurrentSessionEffects.cs +++ b/LiftLog.Ui/Store/CurrentSession/CurrentSessionEffects.cs @@ -26,7 +26,6 @@ IDispatcher dispatcher SessionTarget.WorkoutSession => state.Value.WorkoutSession, SessionTarget.HistorySession => state.Value.HistorySession, SessionTarget.FeedSession => state.Value.FeedSession, - _ => throw new Exception(), }; if (session is not null) await progressRepository.SaveCompletedSessionAsync(session); @@ -54,7 +53,6 @@ public async Task NotifySetTimer(NotifySetTimerAction action, IDispatcher dispat SessionTarget.WorkoutSession => state.Value.WorkoutSession, SessionTarget.HistorySession => state.Value.HistorySession, SessionTarget.FeedSession => state.Value.FeedSession, - _ => throw new Exception(), }; if (session?.NextExercise is not null && session.LastExercise is not null) { @@ -77,7 +75,6 @@ IDispatcher dispatcher SessionTarget.WorkoutSession => state.Value.WorkoutSession, SessionTarget.HistorySession => state.Value.HistorySession, SessionTarget.FeedSession => state.Value.FeedSession, - _ => throw new Exception(), }; if (session?.NextExercise is not null) { diff --git a/LiftLog.Ui/Store/CurrentSession/CurrentSessionReducers.cs b/LiftLog.Ui/Store/CurrentSession/CurrentSessionReducers.cs index c479d164..d8c8ed46 100644 --- a/LiftLog.Ui/Store/CurrentSession/CurrentSessionReducers.cs +++ b/LiftLog.Ui/Store/CurrentSession/CurrentSessionReducers.cs @@ -364,7 +364,6 @@ private static CurrentSessionState WithActiveSession( SessionTarget.WorkoutSession => state with { WorkoutSession = session }, SessionTarget.HistorySession => state with { HistorySession = session }, SessionTarget.FeedSession => state with { FeedSession = session }, - _ => throw new Exception(), }; private static CurrentSessionState WithActiveSession( @@ -383,7 +382,6 @@ private static CurrentSessionState WithActiveSession( HistorySession = sessionMap(state.HistorySession), }, SessionTarget.FeedSession => state with { FeedSession = sessionMap(state.FeedSession) }, - _ => throw new Exception(), }; private static Session? ActiveSession(this CurrentSessionState state, SessionTarget target) => @@ -392,6 +390,5 @@ private static CurrentSessionState WithActiveSession( SessionTarget.WorkoutSession => state.WorkoutSession, SessionTarget.HistorySession => state.HistorySession, SessionTarget.FeedSession => state.FeedSession, - _ => throw new Exception(), }; } diff --git a/LiftLog.Ui/Store/Feed/FeedEffects.Following.cs b/LiftLog.Ui/Store/Feed/FeedEffects.Following.cs index 2906ebe6..af0b723e 100644 --- a/LiftLog.Ui/Store/Feed/FeedEffects.Following.cs +++ b/LiftLog.Ui/Store/Feed/FeedEffects.Following.cs @@ -277,6 +277,20 @@ IDispatcher dispatcher dispatcher.Dispatch(new RemoveFollowerAction(action.User)); }); + private async ValueTask GetUserPublicKey(Guid userId) + { + if (state.Value.FollowedUsers.TryGetValue(userId, out var followedUser)) + { + return followedUser.PublicKey; + } + var userResponse = await feedApiService.GetUserAsync(userId.ToString()); + if (!userResponse.IsSuccess) + { + throw new InvalidOperationException("Failed to fetch user for public key"); + } + return new RsaPublicKey(userResponse.Data.RsaPublicKey); + } + private async Task DecryptIfValid( GetInboxMessageResponse inboxMessage, RsaPrivateKey privateKey @@ -288,7 +302,34 @@ RsaPrivateKey privateKey new RsaEncryptedData(inboxMessage.EncryptedMessage), privateKey ); - return InboxMessageDao.Parser.ParseFrom(decrypted); + var unverifiedInboxMessage = InboxMessageDao.Parser.ParseFrom(decrypted); + + if ( + unverifiedInboxMessage.Signature is null + || unverifiedInboxMessage.Signature.Length == 0 + ) + { + // Temporary until majority of users have updated to new version + return unverifiedInboxMessage; + } + + var payloadBytes = unverifiedInboxMessage.GetPayloadBytes(); + var myUserIdBytes = state.Value.Identity!.Id.ToByteArray(); + byte[] signedPayload = [.. payloadBytes, .. myUserIdBytes]; + var publicKey = await GetUserPublicKey(unverifiedInboxMessage.FromUserId); + + var verified = await encryptionService.VerifyRsaPssSha256Async( + signedPayload, + unverifiedInboxMessage.Signature.ToByteArray(), + publicKey + ); + + if (!verified) + { + throw new InvalidOperationException("Failed to verify inbox message signature"); + } + + return unverifiedInboxMessage; } catch (Exception e) { diff --git a/LiftLog.Web/LiftLog.Web.csproj b/LiftLog.Web/LiftLog.Web.csproj index cfa66866..6334e110 100644 --- a/LiftLog.Web/LiftLog.Web.csproj +++ b/LiftLog.Web/LiftLog.Web.csproj @@ -6,6 +6,8 @@ true enable + + CS8524 diff --git a/LiftLog.Web/Services/WebThemeProvider.cs b/LiftLog.Web/Services/WebThemeProvider.cs index ed037811..c9c317cd 100644 --- a/LiftLog.Web/Services/WebThemeProvider.cs +++ b/LiftLog.Web/Services/WebThemeProvider.cs @@ -71,11 +71,6 @@ public async Task SetSeedColor(uint? seed, ThemePreference themePreference) ThemePreference.FollowSystem => windowThemePrefersDark ? Dark() : Light(), ThemePreference.Light => Light(), ThemePreference.Dark => Dark(), - _ => throw new ArgumentOutOfRangeException( - nameof(themePreference), - themePreference, - null - ), }; dispatcher.Dispatch( new ThemeColorUpdatedAction( From 91f440ad7be2e325f5909ecc6d93179b30a57772 Mon Sep 17 00:00:00 2001 From: Li Date: Wed, 21 Aug 2024 16:56:58 +1000 Subject: [PATCH 2/3] Add from_user_id --- LiftLog.Ui/Models/UserEvent.proto | 2 +- LiftLog.Ui/Services/FeedFollowService.cs | 18 +++++++++++++++--- LiftLog.Ui/Store/Feed/FeedEffects.Following.cs | 14 +++++++++++++- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/LiftLog.Ui/Models/UserEvent.proto b/LiftLog.Ui/Models/UserEvent.proto index 197f259d..29ec4e75 100644 --- a/LiftLog.Ui/Models/UserEvent.proto +++ b/LiftLog.Ui/Models/UserEvent.proto @@ -29,7 +29,7 @@ message InboxMessageDao { FollowResponseDao follow_response = 3; UnFollowNotification unfollow_notification = 4; } - // The signature of the [message + the to_user_id], signed with the private key of the from_user_id + // The signature of the [message +from_user_id+ the to_user_id], signed with the private key of the from_user_id bytes signature = 5; } diff --git a/LiftLog.Ui/Services/FeedFollowService.cs b/LiftLog.Ui/Services/FeedFollowService.cs index 125b6523..c2ed1861 100644 --- a/LiftLog.Ui/Services/FeedFollowService.cs +++ b/LiftLog.Ui/Services/FeedFollowService.cs @@ -22,7 +22,11 @@ public async Task RequestToFollowAUserAsync(FeedIdentity identity, Fe }; inboxMessage.Signature = ByteString.CopyFrom( await encryptionService.SignRsaPssSha256Async( - [.. inboxMessage.GetPayloadBytes(), .. toFollow.Id.ToByteArray()], + [ + .. inboxMessage.GetPayloadBytes(), + .. identity.Id.ToByteArray(), + .. toFollow.Id.ToByteArray(), + ], identity.RsaKeyPair.PrivateKey ) ); @@ -73,7 +77,11 @@ RsaPublicKey userPublicKey }; inboxMessage.Signature = ByteString.CopyFrom( await encryptionService.SignRsaPssSha256Async( - [.. inboxMessage.GetPayloadBytes(), .. request.UserId.ToByteArray()], + [ + .. inboxMessage.GetPayloadBytes(), + .. identity.Id.ToByteArray(), + .. request.UserId.ToByteArray(), + ], identity.RsaKeyPair.PrivateKey ) ); @@ -109,7 +117,11 @@ RsaPublicKey userPublicKey }; inboxMessage.Signature = ByteString.CopyFrom( await encryptionService.SignRsaPssSha256Async( - [.. inboxMessage.GetPayloadBytes(), .. request.UserId.ToByteArray()], + [ + .. inboxMessage.GetPayloadBytes(), + .. identity.Id.ToByteArray(), + .. request.UserId.ToByteArray(), + ], identity.RsaKeyPair.PrivateKey ) ); diff --git a/LiftLog.Ui/Store/Feed/FeedEffects.Following.cs b/LiftLog.Ui/Store/Feed/FeedEffects.Following.cs index af0b723e..42cccad7 100644 --- a/LiftLog.Ui/Store/Feed/FeedEffects.Following.cs +++ b/LiftLog.Ui/Store/Feed/FeedEffects.Following.cs @@ -139,6 +139,16 @@ public async Task UnfollowFeedUser(UnfollowFeedUserAction action, IDispatcher di FollowSecret = action.FeedUser.FollowSecret, }, }; + inboxMessage.Signature = ByteString.CopyFrom( + await encryptionService.SignRsaPssSha256Async( + [ + .. inboxMessage.GetPayloadBytes(), + .. identity.Id.ToByteArray(), + .. action.FeedUser.Id.ToByteArray(), + ], + identity.RsaKeyPair.PrivateKey + ) + ); var encryptedInboxMessage = await encryptionService.EncryptRsaOaepSha256Async( inboxMessage.ToByteArray(), action.FeedUser.PublicKey @@ -315,7 +325,9 @@ unverifiedInboxMessage.Signature is null var payloadBytes = unverifiedInboxMessage.GetPayloadBytes(); var myUserIdBytes = state.Value.Identity!.Id.ToByteArray(); - byte[] signedPayload = [.. payloadBytes, .. myUserIdBytes]; + var subjectUserIdBytes = unverifiedInboxMessage.FromUserId.ToByteArray(); + + byte[] signedPayload = [.. payloadBytes, .. subjectUserIdBytes, .. myUserIdBytes]; var publicKey = await GetUserPublicKey(unverifiedInboxMessage.FromUserId); var verified = await encryptionService.VerifyRsaPssSha256Async( From 458bb2e9755db6597bfef6b00cb1da4deedaac44 Mon Sep 17 00:00:00 2001 From: Li Date: Wed, 21 Aug 2024 17:03:36 +1000 Subject: [PATCH 3/3] Implement encryption --- LiftLog.Lib/Services/OsEncryptionService.cs | 33 +++++++++++++++++++ LiftLog.Web/Services/JsEncryptionService.cs | 14 ++++++++ LiftLog.Web/wwwroot/crypto-utils.ts | 36 +++++++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/LiftLog.Lib/Services/OsEncryptionService.cs b/LiftLog.Lib/Services/OsEncryptionService.cs index 1042b7e4..de8ddcb8 100644 --- a/LiftLog.Lib/Services/OsEncryptionService.cs +++ b/LiftLog.Lib/Services/OsEncryptionService.cs @@ -163,4 +163,37 @@ public ValueTask GenerateRsaKeysAsync() ) ); } + + public Task SignRsaPssSha256Async(byte[] data, RsaPrivateKey privateKey) + { + return Task.Run(() => + { + var rsa = RSA.Create(); + + rsa.ImportPkcs8PrivateKey(privateKey.Pkcs8PrivateKeyBytes, out _); + + var dataHash = SHA256.HashData(data); + + return rsa.SignData(dataHash, HashAlgorithmName.SHA256, RSASignaturePadding.Pss); + }); + } + + public Task VerifyRsaPssSha256Async(byte[] data, byte[] signature, RsaPublicKey publicKey) + { + return Task.Run(() => + { + var rsa = RSA.Create(); + + rsa.ImportSubjectPublicKeyInfo(publicKey.SpkiPublicKeyBytes, out _); + + var dataHash = SHA256.HashData(data); + + return rsa.VerifyData( + dataHash, + signature, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pss + ); + }); + } } diff --git a/LiftLog.Web/Services/JsEncryptionService.cs b/LiftLog.Web/Services/JsEncryptionService.cs index 1d9df8ea..ada3731f 100644 --- a/LiftLog.Web/Services/JsEncryptionService.cs +++ b/LiftLog.Web/Services/JsEncryptionService.cs @@ -64,4 +64,18 @@ public ValueTask GenerateRsaKeysAsync() { return jSRuntime.InvokeAsync("CryptoUtils.generateRsaKeys"); } + + public Task SignRsaPssSha256Async(byte[] data, RsaPrivateKey privateKey) + { + return jSRuntime + .InvokeAsync("CryptoUtils.signRsaPssSha256Async", data, privateKey) + .AsTask(); + } + + public Task VerifyRsaPssSha256Async(byte[] data, byte[] signature, RsaPublicKey publicKey) + { + return jSRuntime + .InvokeAsync("CryptoUtils.verifyRsaPssSha256Async", data, signature, publicKey) + .AsTask(); + } } diff --git a/LiftLog.Web/wwwroot/crypto-utils.ts b/LiftLog.Web/wwwroot/crypto-utils.ts index 1fa53dbb..50871f83 100644 --- a/LiftLog.Web/wwwroot/crypto-utils.ts +++ b/LiftLog.Web/wwwroot/crypto-utils.ts @@ -169,6 +169,40 @@ const decryptRsaOaepSha256Async = async function ( return new Uint8Array(decryptedChunks.reduce((acc, chunk) => [...acc, ...chunk], [] as number[])); }; +const signRsaPssSha256Async = async function (data: Uint8Array, privateKey: RsaPrivateKey): Promise { + const key = await crypto.subtle.importKey( + "pkcs8", + privateKey.pkcs8PrivateKeyBytes, + { + name: "RSA-PSS", + hash: "SHA-256", + } satisfies RsaHashedImportParams, + true, + ["sign"] + ); + const hash = await crypto.subtle.digest("SHA-256", data); + return new Uint8Array(await crypto.subtle.sign("RSA-PSS", key, hash)); +}; + +const verifyRsaPssSha256Async = async function ( + data: Uint8Array, + signature: Uint8Array, + publicKey: RsaPublicKey +): Promise { + const key = await crypto.subtle.importKey( + "spki", + publicKey.spkiPublicKeyBytes, + { + name: "RSA-PSS", + hash: "SHA-256", + } satisfies RsaHashedImportParams, + true, + ["verify"] + ); + const hash = await crypto.subtle.digest("SHA-256", data); + return crypto.subtle.verify("RSA-PSS", key, signature, hash); +}; + var CryptoUtils = { generateAesKey, signRsa256PssAndEncryptAesCbcAsync, @@ -176,6 +210,8 @@ var CryptoUtils = { generateRsaKeys, encryptRsaOaepSha256Async, decryptRsaOaepSha256Async, + signRsaPssSha256Async, + verifyRsaPssSha256Async, }; interface RsaPublicKey {