87 lines
3.5 KiB
C#
87 lines
3.5 KiB
C#
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Xunit;
|
|
|
|
using StellaOps.TestKit;
|
|
namespace StellaOps.EvidenceLocker.Tests;
|
|
|
|
/// <summary>
|
|
/// Golden fixture tests for evidence bundle integrity verification.
|
|
/// These tests verify that checksum/hash computation logic works correctly.
|
|
/// </summary>
|
|
public sealed class GoldenFixturesTests
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void SealedBundle_ComputedHashMatchesRoot()
|
|
{
|
|
// Arrange - Create a minimal bundle structure
|
|
var entries = new[]
|
|
{
|
|
new { canonicalPath = "artifacts/sbom.json", sha256 = "a5b8e9c4f3d2e1b0a7c6d5e4f3c2b1a0e9d8c7b6a5f4e3d2c1b0a9f8e7d6c5b4" },
|
|
new { canonicalPath = "artifacts/provenance.json", sha256 = "b6c9d0e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7" }
|
|
};
|
|
|
|
// Act - Compute the merkle root by hashing entries
|
|
var entryHashes = entries.Select(e => e.sha256).OrderBy(h => h).ToArray();
|
|
var concatenated = string.Join("", entryHashes);
|
|
var merkleRoot = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(concatenated))).ToLowerInvariant();
|
|
|
|
// Assert - Should be able to verify the root was computed from entries
|
|
Assert.NotEmpty(merkleRoot);
|
|
Assert.Equal(64, merkleRoot.Length); // SHA256 produces 64 hex chars
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void PortableBundle_RedactionRemovesTenantInfo()
|
|
{
|
|
// Arrange - Create a bundle with tenant info
|
|
var originalBundle = JsonSerializer.Serialize(new
|
|
{
|
|
bundleId = "test-bundle",
|
|
tenantId = "secret-tenant-123",
|
|
tenantName = "Acme Corp",
|
|
data = new { value = "public" }
|
|
}, JsonOptions);
|
|
|
|
// Act - Simulate redaction by removing tenant fields
|
|
using var doc = JsonDocument.Parse(originalBundle);
|
|
var redactedData = new Dictionary<string, object?>
|
|
{
|
|
["bundleId"] = doc.RootElement.GetProperty("bundleId").GetString(),
|
|
["data"] = new { value = "public" }
|
|
};
|
|
var redactedBundle = JsonSerializer.Serialize(redactedData, JsonOptions);
|
|
|
|
// Assert - Redacted bundle should not contain tenant info
|
|
Assert.Contains("bundleId", redactedBundle);
|
|
Assert.DoesNotContain("tenant", redactedBundle, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Trait("Category", TestCategories.Unit)]
|
|
[Fact]
|
|
public void ReplayRecord_DigestMatchesContent()
|
|
{
|
|
// Arrange - Create sample replay record
|
|
var replayContent = "{\"eventId\":\"evt-001\",\"timestamp\":\"2026-01-22T12:00:00Z\",\"action\":\"promote\"}\n"
|
|
+ "{\"eventId\":\"evt-002\",\"timestamp\":\"2026-01-22T12:01:00Z\",\"action\":\"approve\"}\n";
|
|
var contentBytes = Encoding.UTF8.GetBytes(replayContent);
|
|
|
|
// Act - Compute digest
|
|
var computedHash = "sha256:" + Convert.ToHexString(SHA256.HashData(contentBytes)).ToLowerInvariant();
|
|
|
|
// Assert - Digest should match expected format
|
|
Assert.StartsWith("sha256:", computedHash);
|
|
Assert.Equal(71, computedHash.Length); // "sha256:" (7) + 64 hex chars
|
|
|
|
// Verify digest is deterministic
|
|
var recomputedHash = "sha256:" + Convert.ToHexString(SHA256.HashData(contentBytes)).ToLowerInvariant();
|
|
Assert.Equal(computedHash, recomputedHash);
|
|
}
|
|
}
|