fix(tests): Fix Test4 broken JSON, Test2 false positive, add cleanup trap and Test9/Test10

This commit is contained in:
ochenstarik-ui 2026-08-10 20:07:49 +07:00
parent 4f583c9a03
commit ad396fd6a2
2 changed files with 96 additions and 24 deletions

View file

@ -49,6 +49,16 @@ namespace ServerMonitorManager.Desktop.Security.Tests
]
}";
// Release JSON without the .sig asset — used by Test4
private const string NoSigReleaseJson = @"
{
""tag_name"": ""v0.1.0-alpha.9"",
""assets"": [
{ ""name"": ""server-monitor-manager-manifest.json"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.json"" },
{ ""name"": ""ServerMonitorManager-win-x64.msix"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/ServerMonitorManager-win-x64.msix"" }
]
}";
private const string ValidManifestJson = @"
{
""version"": ""v0.1.0-alpha.9"",
@ -130,8 +140,7 @@ namespace ServerMonitorManager.Desktop.Security.Tests
[Fact]
public async Task Test4_MissingSignature_Rejected()
{
var noSigRelease = ValidReleaseJson.Replace("{ \"name\": \"server-monitor-manager-manifest.sig\"", "//");
var http = new MockHttpTransport { GetStringAsyncFunc = _ => Task.FromResult(noSigRelease) };
var http = new MockHttpTransport { GetStringAsyncFunc = _ => Task.FromResult(NoSigReleaseJson) };
var service = new UpdateService(http, new MockSignatureVerifier(), new MockFileStorage());
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckForUpdatesAsync());
@ -154,7 +163,7 @@ namespace ServerMonitorManager.Desktop.Security.Tests
}
[Fact]
public async Task Test8_UpdateActionNotShownUntilVerified()
public async Task Test6_UpdateActionNotShownUntilVerified()
{
var http = new MockHttpTransport { GetStringAsyncFunc = url => Task.FromResult(url.EndsWith("releases/latest") ? ValidReleaseJson : ValidManifestJson) };
bool signatureVerified = false;
@ -173,7 +182,7 @@ namespace ServerMonitorManager.Desktop.Security.Tests
}
[Fact]
public async Task Test9_PreReleaseChannel_Used()
public async Task Test7_PreReleaseChannel_Used()
{
var preReleaseJson = "[" + ValidReleaseJson.Replace("v0.1.0-alpha.9", "v0.1.0-alpha.10") + "]";
var preReleaseManifest = ValidManifestJson.Replace("v0.1.0-alpha.9", "v0.1.0-alpha.10");
@ -195,7 +204,7 @@ namespace ServerMonitorManager.Desktop.Security.Tests
}
[Fact]
public async Task Test10_UrlAssetFromAnotherTag_Rejected()
public async Task Test8_UrlAssetFromAnotherTag_Rejected()
{
// Msix URL points to a different tag
var maliciousRelease = ValidReleaseJson.Replace("v0.1.0-alpha.9/ServerMonitorManager-win-x64.msix", "v0.1.0-alpha.8/ServerMonitorManager-win-x64.msix");
@ -206,5 +215,54 @@ namespace ServerMonitorManager.Desktop.Security.Tests
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckForUpdatesAsync());
Assert.Contains("does not match the release tag", ex.Message);
}
[Fact]
public async Task Test9_ManifestVersionMismatch_Rejected()
{
// Manifest says v0.1.0-alpha.8 but release tag says v0.1.0-alpha.9
var mismatchedManifest = ValidManifestJson.Replace("v0.1.0-alpha.9", "v0.1.0-alpha.8");
var http = new MockHttpTransport
{
GetStringAsyncFunc = url =>
{
if (url.EndsWith("releases/latest")) return Task.FromResult(ValidReleaseJson);
if (url.EndsWith(".json")) return Task.FromResult(mismatchedManifest);
if (url.EndsWith(".sig")) return Task.FromResult("valid-sig");
return Task.FromResult("");
}
};
var service = new UpdateService(http, new MockSignatureVerifier(), new MockFileStorage());
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckForUpdatesAsync());
Assert.Contains("version", ex.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task Test10_MissingMsixAsset_Rejected()
{
// Release JSON without the MSIX asset
var noMsixRelease = @"
{
""tag_name"": ""v0.1.0-alpha.9"",
""assets"": [
{ ""name"": ""server-monitor-manager-manifest.json"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.json"" },
{ ""name"": ""server-monitor-manager-manifest.sig"", ""browser_download_url"": ""https://example.com/releases/download/v0.1.0-alpha.9/server-monitor-manager-manifest.sig"" }
]
}";
var http = new MockHttpTransport
{
GetStringAsyncFunc = url =>
{
if (url.EndsWith("releases/latest")) return Task.FromResult(noMsixRelease);
if (url.EndsWith(".json")) return Task.FromResult(ValidManifestJson);
if (url.EndsWith(".sig")) return Task.FromResult("valid-sig");
return Task.FromResult("");
}
};
var service = new UpdateService(http, new MockSignatureVerifier(), new MockFileStorage());
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => service.CheckForUpdatesAsync());
Assert.Contains("MSIX", ex.Message, StringComparison.OrdinalIgnoreCase);
}
}
}

View file

@ -1,6 +1,12 @@
#!/usr/bin/env bash
set -Eeuo pipefail
CLEANUP_FILES=()
cleanup() {
rm -f "${CLEANUP_FILES[@]}"
}
trap cleanup EXIT
echo "Running negative tests for manifest verification..."
if ! command -v cosign &> /dev/null; then
@ -12,12 +18,15 @@ fi
export COSIGN_PASSWORD=""
cosign generate-key-pair
export SMM_TEST_PUBKEY="cosign.pub"
CLEANUP_FILES+=(cosign.key cosign.pub)
ARCHIVE_NAME="test-archive.tar.gz"
echo "archive content" > "$ARCHIVE_NAME"
ARCHIVE_HASH=$(sha256sum "$ARCHIVE_NAME" | awk '{print $1}')
CLEANUP_FILES+=("$ARCHIVE_NAME")
cat <<EOF > manifest.json
# Use the canonical manifest name that verify_archive() expects
cat <<EOF > server-monitor-manager-manifest.json
{
"hashes": {
"$ARCHIVE_NAME": "$ARCHIVE_HASH"
@ -25,10 +34,11 @@ cat <<EOF > manifest.json
}
EOF
cosign sign-blob --yes --key cosign.key --output-signature manifest.sig manifest.json
cosign sign-blob --yes --key cosign.key --output-signature server-monitor-manager-manifest.sig server-monitor-manager-manifest.json
CLEANUP_FILES+=(server-monitor-manager-manifest.json server-monitor-manager-manifest.sig)
echo "Test 1: Valid signature and hash"
if ! bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest manifest.json manifest.sig; then
if ! bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json server-monitor-manager-manifest.sig; then
echo "FAIL: Valid payload rejected"
exit 1
fi
@ -36,7 +46,8 @@ echo "PASS: Valid payload accepted"
echo "Test 2: Altered byte in archive"
echo "altered content" > "$ARCHIVE_NAME"
# Note: we need to test archive verification for altered archive! The old test used verify-manifest which doesn't check archive.
# verify-release will call verify_archive → verify_manifest (signature check) then sha256 (hash check).
# The manifest was signed with the original hash, so the archive hash won't match.
if bash deploy/ochenstarik-server-monitor-manager.sh verify-release "$ARCHIVE_NAME" >/dev/null 2>&1; then
echo "FAIL: Altered archive accepted"
exit 1
@ -46,39 +57,42 @@ echo "PASS: Altered archive rejected"
echo "Test 3: Substituted hash in manifest without resigning"
# Restore archive
echo "archive content" > "$ARCHIVE_NAME"
# Corrupt manifest
cat <<EOF > manifest.json
# Corrupt manifest (but don't re-sign — signature should now be invalid)
cat <<EOF > server-monitor-manager-manifest.json
{
"hashes": {
"$ARCHIVE_NAME": "0000000000000000000000000000000000000000000000000000000000000000"
}
}
EOF
if bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest manifest.json manifest.sig >/dev/null 2>&1; then
if bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json server-monitor-manager-manifest.sig >/dev/null 2>&1; then
echo "FAIL: Substituted hash accepted"
exit 1
fi
echo "PASS: Substituted hash rejected"
echo "Test 4: Manifest without signature"
if bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest manifest.json "" >/dev/null 2>&1; then
if bash deploy/ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json "" >/dev/null 2>&1; then
echo "FAIL: Missing signature accepted"
exit 1
fi
echo "PASS: Missing signature rejected"
echo "Test 5: Real alpha.8 manifest fallback matching"
echo "Test 5: Real alpha.8 manifest fallback matching (REQUIRES_NETWORK)"
ALPHA8_ARCHIVE="ochenstarik-server-monitor-manager-linux-x64.tar.gz"
wget -qO "$ALPHA8_ARCHIVE" https://github.com/ochenstarik-ui/server-monitor-manager/releases/download/v0.1.0-alpha.8/server-monitor-manager-linux-x64.tar.gz
wget -qO server-monitor-manager-manifest.json https://github.com/ochenstarik-ui/server-monitor-manager/releases/download/v0.1.0-alpha.8/server-monitor-manager-manifest.json
wget -qO server-monitor-manager-manifest.sig https://github.com/ochenstarik-ui/server-monitor-manager/releases/download/v0.1.0-alpha.8/server-monitor-manager-manifest.sig
unset SMM_TEST_PUBKEY
if ! bash deploy/ochenstarik-server-monitor-manager.sh verify-release "$ALPHA8_ARCHIVE" >/dev/null 2>&1; then
if ! wget -qO "$ALPHA8_ARCHIVE" https://github.com/ochenstarik-ui/server-monitor-manager/releases/download/v0.1.0-alpha.8/server-monitor-manager-linux-x64.tar.gz; then
echo "SKIP: Could not download alpha.8 archive (network unavailable)"
else
wget -qO server-monitor-manager-manifest.json https://github.com/ochenstarik-ui/server-monitor-manager/releases/download/v0.1.0-alpha.8/server-monitor-manager-manifest.json
wget -qO server-monitor-manager-manifest.sig https://github.com/ochenstarik-ui/server-monitor-manager/releases/download/v0.1.0-alpha.8/server-monitor-manager-manifest.sig
CLEANUP_FILES+=("$ALPHA8_ARCHIVE")
# Use keyless verification against real Sigstore/Rekor (requires network)
unset SMM_TEST_PUBKEY
if ! bash deploy/ochenstarik-server-monitor-manager.sh verify-release "$ALPHA8_ARCHIVE" >/dev/null 2>&1; then
echo "FAIL: Alpha.8 real release verification failed"
exit 1
fi
echo "PASS: Alpha.8 real release verification succeeded"
fi
echo "PASS: Alpha.8 real release verification succeeded"
echo "All tests passed."
rm -f cosign.key cosign.pub manifest.json manifest.sig "$ARCHIVE_NAME" "$ALPHA8_ARCHIVE" server-monitor-manager-manifest.json server-monitor-manager-manifest.sig