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;