fix(security): Add version mismatch check, MSIX cleanup on hash failure, TraceSource diagnostics, csproj fix
- UpdateService: manifest version must match release tag_name (prevents cross-version attacks) - UpdateService: corrupted MSIX deleted immediately on hash mismatch - UpdateService: TraceSource logging for all verification steps - UpdateService: Trust anchor constants with docs/release-policy.md reference - Bootstrap: docs/release-policy.md reference next to identity constants - Test csproj: add UpdateService.cs Compile Include (tests won't compile without this)
This commit is contained in:
parent
ad396fd6a2
commit
d21c0c5d22
3 changed files with 22 additions and 0 deletions
|
|
@ -23,6 +23,7 @@ readonly WG_DIR="${ETC_DIR}/wireguard"
|
|||
readonly FIREWALL_UNIT="ochenstarik-smm-firewall.service"
|
||||
readonly MESH_NETWORK="10.77.0.0/24"
|
||||
readonly HUB_MESH_ADDRESS="10.77.0.1/24"
|
||||
# Trust anchors — see docs/release-policy.md for the full signing and identity contract.
|
||||
readonly COSIGN_ISSUER="https://token.actions.githubusercontent.com"
|
||||
readonly COSIGN_IDENTITY_REGEXP="^https://github.com/ochenstarik-ui/server-monitor-manager/\.github/workflows/linux-release\.yml@refs/tags/v.*$"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
|
|
@ -163,6 +164,11 @@ public class DefaultFileStorage : IFileStorage
|
|||
public class UpdateService
|
||||
{
|
||||
private const string Repository = "ochenstarik-ui/server-monitor-manager";
|
||||
// Trust anchors pinned per docs/release-policy.md — do NOT fetch these from the release being verified.
|
||||
private const string CosignIssuer = "https://token.actions.githubusercontent.com";
|
||||
private const string CosignIdentityRegexp = @"^https://github\.com/ochenstarik-ui/server-monitor-manager/\.github/workflows/.*\.yml@refs/tags/v.*$";
|
||||
|
||||
private static readonly TraceSource Log = new("ServerMonitorManager.UpdateService", SourceLevels.All);
|
||||
|
||||
private readonly IHttpTransport _http;
|
||||
private readonly ISignatureVerifier _signatureVerifier;
|
||||
|
|
@ -239,6 +245,13 @@ public class UpdateService
|
|||
}
|
||||
|
||||
var version = manifestNode["version"]?.GetValue<string>();
|
||||
|
||||
// Manifest version must match the release tag to prevent downgrade/cross-version attacks
|
||||
if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(releaseTagName) && version != releaseTagName)
|
||||
{
|
||||
Log.TraceEvent(TraceEventType.Error, 0, $"Manifest version '{version}' does not match release tag '{releaseTagName}'. Rejecting.");
|
||||
throw new InvalidOperationException($"Manifest version '{version}' does not match release tag '{releaseTagName}'. Update rejected.");
|
||||
}
|
||||
|
||||
// 3. Verify manifest signature BEFORE showing update action
|
||||
var tempFolder = _fileStorage.GetTempFolder();
|
||||
|
|
@ -247,7 +260,9 @@ public class UpdateService
|
|||
await _fileStorage.WriteAllTextAsync(manifestPath, manifestJson, cancellationToken);
|
||||
await _fileStorage.WriteAllTextAsync(sigPath, manifestSig, cancellationToken);
|
||||
|
||||
Log.TraceEvent(TraceEventType.Information, 0, "Verifying manifest signature...");
|
||||
await _signatureVerifier.VerifySignatureAsync(sigPath, manifestPath, cancellationToken);
|
||||
Log.TraceEvent(TraceEventType.Information, 0, "Manifest signature verified successfully.");
|
||||
|
||||
var msixUrl = assets.FirstOrDefault(a => a?["name"]?.GetValue<string>() == "ServerMonitorManager-win-x64.msix")?["browser_download_url"]?.GetValue<string>();
|
||||
if (msixUrl is null)
|
||||
|
|
@ -270,12 +285,17 @@ public class UpdateService
|
|||
var tempFolder = _fileStorage.GetTempFolder();
|
||||
var msixPath = Path.Combine(tempFolder, "ServerMonitorManager-win-x64.msix");
|
||||
|
||||
Log.TraceEvent(TraceEventType.Information, 0, $"Downloading update from {updateInfo.DownloadUrl}...");
|
||||
await _http.DownloadFileAsync(updateInfo.DownloadUrl, msixPath, progressCallback, cancellationToken);
|
||||
|
||||
if (!VerifyFileHash(msixPath, updateInfo.ExpectedHash))
|
||||
{
|
||||
// Delete the corrupted file immediately
|
||||
try { File.Delete(msixPath); } catch { /* best-effort cleanup */ }
|
||||
Log.TraceEvent(TraceEventType.Error, 0, "MSIX hash mismatch — downloaded file deleted.");
|
||||
throw new InvalidOperationException("Update MSIX hash mismatch. Update rejected.");
|
||||
}
|
||||
Log.TraceEvent(TraceEventType.Information, 0, "MSIX hash verified successfully.");
|
||||
}
|
||||
|
||||
public void InstallUpdate()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\src\ServerMonitorManager.Desktop\UpdateService.cs" Link="UpdateService.cs" />
|
||||
<Compile Include="..\..\src\ServerMonitorManager.Desktop\SshPrivateKeySession.cs" Link="SshPrivateKeySession.cs" />
|
||||
<Compile Include="..\..\src\ServerMonitorManager.Desktop\SshHostKeyTrust.cs" Link="SshHostKeyTrust.cs" />
|
||||
<Compile Include="..\..\src\ServerMonitorManager.Desktop\SshConnectionArguments.cs" Link="SshConnectionArguments.cs" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue