tests fixes and some product advisories tunes ups

This commit is contained in:
master
2026-01-30 07:57:43 +02:00
parent 644887997c
commit 55744f6a39
345 changed files with 26290 additions and 2267 deletions

View File

@@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
@@ -9,15 +10,13 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
<PackageReference Include="Moq" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="coverlet.collector" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\__Libraries\StellaOps.Attestor.EvidencePack\StellaOps.Attestor.EvidencePack.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.TestKit\StellaOps.TestKit.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,258 @@
// Copyright (c) StellaOps. All rights reserved.
// Licensed under the BUSL-1.1 license.
// Advisory: EU CRA/NIS2 compliance - Sealed Audit-Pack replay_log.json tests
using System.Collections.Immutable;
using System.Text.Json;
using FluentAssertions;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Attestor.EvidencePack.Models;
using StellaOps.Attestor.EvidencePack.Services;
namespace StellaOps.Attestor.EvidencePack.Tests;
/// <summary>
/// Unit tests for VerificationReplayLogBuilder.
/// Tests the replay_log.json generation for EU CRA/NIS2 compliance.
/// </summary>
public class VerificationReplayLogBuilderTests
{
private readonly FakeTimeProvider _timeProvider;
private readonly VerificationReplayLogBuilder _builder;
public VerificationReplayLogBuilderTests()
{
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2026, 1, 29, 12, 0, 0, TimeSpan.Zero));
_builder = new VerificationReplayLogBuilder(_timeProvider);
}
[Fact]
public void Build_WithMinimalRequest_ReturnsValidReplayLog()
{
// Arrange
var request = new VerificationReplayLogRequest
{
ArtifactRef = "oci://registry.example.com/app:v1.0.0@sha256:abc123"
};
// Act
var log = _builder.Build(request);
// Assert
log.Should().NotBeNull();
log.SchemaVersion.Should().Be("1.0.0");
log.ArtifactRef.Should().Be("oci://registry.example.com/app:v1.0.0@sha256:abc123");
log.VerifierVersion.Should().Be("stellaops-attestor/1.0.0");
log.Result.Should().Be("pass");
log.ReplayId.Should().StartWith("replay_");
}
[Fact]
public void Build_WithFullVerificationRequest_ReturnsAllSteps()
{
// Arrange
var request = new VerificationReplayLogRequest
{
ArtifactRef = "oci://registry.example.com/app:v1.0.0@sha256:abc123",
SbomPath = "sbom/app.cdx.json",
CanonicalSbomDigest = "sha256:sbomdigest123",
DsseEnvelopePath = "attestations/app.dsse.json",
DsseSubjectDigest = "sha256:sbomdigest123",
DsseSignatureValid = true,
SigningKeyId = "cosign-key-1",
SignatureAlgorithm = "ecdsa-p256",
SigningKeyFingerprint = "SHA256:keyfingerprint123",
CosignPublicKeyPath = "cosign.pub",
RekorLogId = "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d",
RekorLogIndex = 12345678,
RekorTreeSize = 99999999,
RekorRootHash = "sha256:merklerootabc",
RekorIntegratedTime = 1706529600,
InclusionProofPath = "rekor-proofs/log-entries/12345678.json",
RekorInclusionValid = true,
CheckpointPath = "rekor-proofs/checkpoint.json",
CheckpointValid = true,
RekorPublicKeyPath = "rekor-public-key.pub",
RekorPublicKeyId = "rekor-key-1"
};
// Act
var log = _builder.Build(request);
// Assert
log.Result.Should().Be("pass");
log.Steps.Should().HaveCount(5);
// Step 1: Canonical SBOM digest
log.Steps[0].Step.Should().Be(1);
log.Steps[0].Action.Should().Be("compute_canonical_sbom_digest");
log.Steps[0].Input.Should().Be("sbom/app.cdx.json");
log.Steps[0].Output.Should().Be("sha256:sbomdigest123");
log.Steps[0].Result.Should().Be("pass");
// Step 2: DSSE subject match
log.Steps[1].Step.Should().Be(2);
log.Steps[1].Action.Should().Be("verify_dsse_subject_match");
log.Steps[1].Expected.Should().Be("sha256:sbomdigest123");
log.Steps[1].Actual.Should().Be("sha256:sbomdigest123");
log.Steps[1].Result.Should().Be("pass");
// Step 3: DSSE signature
log.Steps[2].Step.Should().Be(3);
log.Steps[2].Action.Should().Be("verify_dsse_signature");
log.Steps[2].KeyId.Should().Be("cosign-key-1");
log.Steps[2].Result.Should().Be("pass");
// Step 4: Rekor inclusion
log.Steps[3].Step.Should().Be(4);
log.Steps[3].Action.Should().Be("verify_rekor_inclusion");
log.Steps[3].Result.Should().Be("pass");
// Step 5: Rekor checkpoint
log.Steps[4].Step.Should().Be(5);
log.Steps[4].Action.Should().Be("verify_rekor_checkpoint");
log.Steps[4].Result.Should().Be("pass");
// Verification keys
log.VerificationKeys.Should().HaveCount(2);
log.VerificationKeys[0].Type.Should().Be("cosign");
log.VerificationKeys[0].Path.Should().Be("cosign.pub");
log.VerificationKeys[1].Type.Should().Be("rekor");
log.VerificationKeys[1].Path.Should().Be("rekor-public-key.pub");
// Rekor info
log.Rekor.Should().NotBeNull();
log.Rekor!.LogId.Should().Be("c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d");
log.Rekor.LogIndex.Should().Be(12345678);
log.Rekor.TreeSize.Should().Be(99999999);
}
[Fact]
public void Build_WithFailedDsseSignature_ReturnsFailResult()
{
// Arrange
var request = new VerificationReplayLogRequest
{
ArtifactRef = "oci://registry.example.com/app:v1.0.0@sha256:abc123",
SbomPath = "sbom/app.cdx.json",
CanonicalSbomDigest = "sha256:sbomdigest123",
DsseEnvelopePath = "attestations/app.dsse.json",
DsseSubjectDigest = "sha256:sbomdigest123",
DsseSignatureValid = false,
DsseSignatureError = "Invalid signature: key mismatch",
SigningKeyId = "cosign-key-1",
CosignPublicKeyPath = "cosign.pub"
};
// Act
var log = _builder.Build(request);
// Assert
log.Result.Should().Be("fail");
log.Steps.Should().Contain(s => s.Action == "verify_dsse_signature" && s.Result == "fail");
log.Steps.First(s => s.Action == "verify_dsse_signature").Error
.Should().Be("Invalid signature: key mismatch");
}
[Fact]
public void Build_WithMismatchedDigests_ReturnsFailResult()
{
// Arrange
var request = new VerificationReplayLogRequest
{
ArtifactRef = "oci://registry.example.com/app:v1.0.0@sha256:abc123",
SbomPath = "sbom/app.cdx.json",
CanonicalSbomDigest = "sha256:computeddigest",
DsseSubjectDigest = "sha256:differentdigest"
};
// Act
var log = _builder.Build(request);
// Assert
log.Result.Should().Be("fail");
var mismatchStep = log.Steps.First(s => s.Action == "verify_dsse_subject_match");
mismatchStep.Result.Should().Be("fail");
mismatchStep.Expected.Should().Be("sha256:differentdigest");
mismatchStep.Actual.Should().Be("sha256:computeddigest");
mismatchStep.Error.Should().Contain("does not match");
}
[Fact]
public void Serialize_ReturnsValidJson()
{
// Arrange
var request = new VerificationReplayLogRequest
{
ArtifactRef = "oci://registry.example.com/app:v1.0.0@sha256:abc123",
SbomPath = "sbom/app.cdx.json",
CanonicalSbomDigest = "sha256:sbomdigest123"
};
var log = _builder.Build(request);
// Act
var json = _builder.Serialize(log);
// Assert
json.Should().NotBeNullOrEmpty();
json.Should().Contain("\"schema_version\"");
json.Should().Contain("\"replay_id\"");
json.Should().Contain("\"artifact_ref\"");
json.Should().Contain("\"steps\"");
json.Should().Contain("\"compute_canonical_sbom_digest\"");
// Should be valid JSON
var parsed = JsonDocument.Parse(json);
parsed.RootElement.GetProperty("schema_version").GetString().Should().Be("1.0.0");
}
[Fact]
public void Build_WithMetadata_IncludesMetadataInLog()
{
// Arrange
var request = new VerificationReplayLogRequest
{
ArtifactRef = "oci://registry.example.com/app:v1.0.0@sha256:abc123",
Metadata = ImmutableDictionary<string, string>.Empty
.Add("compliance_framework", "EU_CRA_NIS2")
.Add("auditor", "external-auditor-id")
};
// Act
var log = _builder.Build(request);
// Assert
log.Metadata.Should().NotBeNull();
log.Metadata!["compliance_framework"].Should().Be("EU_CRA_NIS2");
log.Metadata["auditor"].Should().Be("external-auditor-id");
}
[Fact]
public void Build_GeneratesUniqueReplayId()
{
// Arrange
var request1 = new VerificationReplayLogRequest { ArtifactRef = "artifact1" };
var request2 = new VerificationReplayLogRequest { ArtifactRef = "artifact2" };
// Act
var log1 = _builder.Build(request1);
var log2 = _builder.Build(request2);
// Assert
log1.ReplayId.Should().NotBe(log2.ReplayId);
}
[Fact]
public void Build_UsesProvidedTimeProvider()
{
// Arrange
var expectedTime = new DateTimeOffset(2026, 1, 29, 12, 0, 0, TimeSpan.Zero);
var request = new VerificationReplayLogRequest { ArtifactRef = "test" };
// Act
var log = _builder.Build(request);
// Assert
log.VerifiedAt.Should().Be(expectedTime);
}
}