diff --git a/StabilityMatrix/StabilityMatrix.csproj b/StabilityMatrix/StabilityMatrix.csproj
index f4947bb6..b5e04e29 100644
--- a/StabilityMatrix/StabilityMatrix.csproj
+++ b/StabilityMatrix/StabilityMatrix.csproj
@@ -33,6 +33,7 @@
+
diff --git a/StabilityMatrix/Updater/SignatureChecker.cs b/StabilityMatrix/Updater/SignatureChecker.cs
new file mode 100644
index 00000000..3a1159a5
--- /dev/null
+++ b/StabilityMatrix/Updater/SignatureChecker.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Text;
+using NSec.Cryptography;
+
+namespace StabilityMatrix.Updater;
+
+public class SignatureChecker
+{
+ private static readonly SignatureAlgorithm Algorithm = SignatureAlgorithm.Ed25519;
+ private const string UpdatePublicKey =
+ "-----BEGIN PUBLIC KEY-----\n" +
+ "MCowBQYDK2VwAyEAqYXhKG1b0iOMnAZGBSBdFlFEWpFBIbIPQk0TtyE2SfI=\n" +
+ "-----END PUBLIC KEY-----\n";
+
+ private readonly PublicKey publicKey;
+
+ ///
+ /// Initializes a new instance of SignatureChecker.
+ ///
+ /// Pkix format public key. Defaults to update verification key.
+ public SignatureChecker(string? publicKeyPkix = null)
+ {
+ publicKey = PublicKey.Import(
+ Algorithm,
+ Encoding.ASCII.GetBytes(publicKeyPkix ?? UpdatePublicKey),
+ KeyBlobFormat.PkixPublicKeyText);
+ }
+
+ ///
+ /// Verifies the signature of provided data.
+ ///
+ /// Data to verify
+ /// Signature in base64 encoding
+ /// True if verified
+ public bool Verify(string data, string signature)
+ {
+ var signatureBytes = Convert.FromBase64String(signature);
+ return Algorithm.Verify(publicKey, Encoding.UTF8.GetBytes(data), signatureBytes);
+ }
+
+ ///
+ /// Verifies the signature of provided data.
+ ///
+ /// Data to verify
+ /// Signature in base64 encoding
+ /// True if verified
+ public bool Verify(ReadOnlySpan data, ReadOnlySpan signature)
+ {
+ return Algorithm.Verify(publicKey, data, signature);
+ }
+}
diff --git a/StabilityMatrix/Updater/UpdateHelper.cs b/StabilityMatrix/Updater/UpdateHelper.cs
index 630c483b..3dc9d477 100644
--- a/StabilityMatrix/Updater/UpdateHelper.cs
+++ b/StabilityMatrix/Updater/UpdateHelper.cs
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using System.Windows.Threading;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using StabilityMatrix.Extensions;
using StabilityMatrix.Helper;
using StabilityMatrix.Models;
using StabilityMatrix.Models.Configs;
@@ -62,6 +63,21 @@ public class UpdateHelper : IUpdateHelper
httpClientName: "UpdateClient");
}
+
+ ///
+ /// Data for use in signature verification.
+ /// Semicolon separated string of fields:
+ /// "version, releaseDate, channel, type, url, changelog, hashBlake3"
+ ///
+ private string GetUpdateInfoSignedData(UpdateInfo updateInfo)
+ {
+ var channel = updateInfo.Channel.GetStringValue().ToLowerInvariant();
+ var date = updateInfo.ReleaseDate.ToString("yyyy-MM-ddTHH:mm:ss.ffffffzzz");
+ return $"{updateInfo.Version};{date};{channel};" +
+ $"{(int) updateInfo.Type};{updateInfo.DownloadUrl};{updateInfo.ChangelogUrl};" +
+ $"{updateInfo.HashBlake3}";
+ }
+
private async Task CheckForUpdate()
{
try
@@ -83,6 +99,19 @@ public class UpdateHelper : IUpdateHelper
logger.LogError("UpdateInfo is null");
return;
}
+ logger.LogInformation("UpdateInfo signature: {Signature}", updateInfo.Signature);
+
+ var updateInfoSignData = GetUpdateInfoSignedData(updateInfo);
+ logger.LogInformation("UpdateInfo signed data: {SignData}", updateInfoSignData);
+
+ // Verify signature
+ var checker = new SignatureChecker();
+ if (!checker.Verify(updateInfoSignData, updateInfo.Signature))
+ {
+ logger.LogError("UpdateInfo signature is invalid: {Info}", updateInfo);
+ return;
+ }
+ logger.LogInformation("UpdateInfo signature verified");
var currentVersion = Assembly.GetExecutingAssembly().GetName().Version;