Add determinism tests for verdict artifact generation and update SHA256 sums script

- Implemented comprehensive tests for verdict artifact generation to ensure deterministic outputs across various scenarios, including identical inputs, parallel execution, and change ordering.
- Created helper methods for generating sample verdict inputs and computing canonical hashes.
- Added tests to validate the stability of canonical hashes, proof spine ordering, and summary statistics.
- Introduced a new PowerShell script to update SHA256 sums for files, ensuring accurate hash generation and file integrity checks.
This commit is contained in:
StellaOps Bot
2025-12-24 02:17:34 +02:00
parent e59921374e
commit 7503c19b8f
390 changed files with 37389 additions and 5380 deletions

View File

@@ -29,6 +29,7 @@ using StellaOps.Attestor.Core.Bulk;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Serilog.Context;
using StellaOps.Cryptography.DependencyInjection;
using StellaOps.Router.AspNet;
const string ConfigurationSection = "attestor";
@@ -326,6 +327,13 @@ builder.WebHost.ConfigureKestrel(kestrel =>
});
});
// Stella Router integration
var routerOptions = builder.Configuration.GetSection("Attestor:Router").Get<StellaRouterOptionsBase>();
builder.Services.TryAddStellaRouter(
serviceName: "attestor",
version: typeof(Program).Assembly.GetName().Version?.ToString() ?? "1.0.0",
routerOptions: routerOptions);
var app = builder.Build();
app.UseSerilogRequestLogging();
@@ -359,6 +367,7 @@ app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.TryUseStellaRouter(routerOptions);
app.MapHealthChecks("/health/ready");
app.MapHealthChecks("/health/live");
@@ -608,6 +617,9 @@ app.MapGet("/api/v1/rekor/verify:bulk/{jobId}", async (
return Results.Ok(BulkVerificationContracts.MapJob(job));
}).RequireAuthorization("attestor:write");
// Refresh Router endpoint cache
app.TryRefreshStellaRouterEndpoints(routerOptions);
app.Run();
static async Task<IResult> GetAttestationDetailResultAsync(

View File

@@ -27,5 +27,6 @@
<ProjectReference Include="../../../Authority/StellaOps.Authority/StellaOps.Auth.Client/StellaOps.Auth.Client.csproj" />
<ProjectReference Include="../../../Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/StellaOps.Auth.ServerIntegration.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Attestor.StandardPredicates/StellaOps.Attestor.StandardPredicates.csproj" />
<ProjectReference Include="../../../__Libraries/StellaOps.Router.AspNet/StellaOps.Router.AspNet.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,297 @@
// -----------------------------------------------------------------------------
// DsseEnvelopeDeterminismTests.cs
// Sprint: SPRINT_5100_0009_0007_attestor_tests
// Tasks: ATTESTOR-5100-001, ATTESTOR-5100-002
// Description: Model L0 tests for DSSE envelope generation and verification
// -----------------------------------------------------------------------------
using System.Text;
using System.Text.Json;
using FluentAssertions;
using StellaOps.Attestor.Envelope;
using StellaOps.Attestor.ProofChain.Builders;
using StellaOps.Attestor.ProofChain.Statements;
using Xunit;
namespace StellaOps.Attestor.ProofChain.Tests.Envelope;
/// <summary>
/// Tests for DSSE envelope generation and verification.
/// Implements Model L0 test requirements:
/// - ATTESTOR-5100-001: DSSE envelope generation tests
/// - ATTESTOR-5100-002: DSSE envelope verification tests
/// </summary>
[Trait("Category", "Unit")]
[Trait("Category", "Determinism")]
[Trait("Category", "DsseEnvelope")]
public sealed class DsseEnvelopeDeterminismTests
{
private static readonly DateTimeOffset FixedTime = new(2025, 12, 24, 12, 0, 0, TimeSpan.Zero);
// ATTESTOR-5100-001: DSSE envelope generation tests
[Fact]
public void DsseEnvelope_Generation_CreatesValidStructure()
{
// Arrange
var payload = Encoding.UTF8.GetBytes("""{"test":"payload"}""");
var signature = DsseSignature.FromBytes(new byte[] { 0x01, 0x02, 0x03 }, "test-key-id");
// Act
var envelope = new DsseEnvelope(
payloadType: "application/vnd.in-toto+json",
payload: payload,
signatures: new[] { signature });
// Assert
envelope.PayloadType.Should().Be("application/vnd.in-toto+json");
envelope.Payload.Length.Should().Be(payload.Length);
envelope.Signatures.Should().HaveCount(1);
envelope.Signatures[0].KeyId.Should().Be("test-key-id");
}
[Fact]
public void DsseEnvelope_Generation_RequiresAtLeastOneSignature()
{
// Arrange
var payload = Encoding.UTF8.GetBytes("test");
// Act
var act = () => new DsseEnvelope(
payloadType: "application/vnd.in-toto+json",
payload: payload,
signatures: Array.Empty<DsseSignature>());
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*At least one signature*");
}
[Fact]
public void DsseEnvelope_Generation_RequiresPayloadType()
{
// Arrange
var payload = Encoding.UTF8.GetBytes("test");
var signature = DsseSignature.FromBytes(new byte[] { 0x01 }, "key");
// Act
var act = () => new DsseEnvelope(
payloadType: "",
payload: payload,
signatures: new[] { signature });
// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*payloadType*");
}
[Fact]
public void DsseEnvelope_Generation_NormalizesSignatureOrder()
{
// Arrange
var payload = Encoding.UTF8.GetBytes("test");
var sig1 = DsseSignature.FromBytes(new byte[] { 0x01 }, "z-key");
var sig2 = DsseSignature.FromBytes(new byte[] { 0x02 }, "a-key");
var sig3 = DsseSignature.FromBytes(new byte[] { 0x03 }, null);
// Act
var envelope = new DsseEnvelope(
payloadType: "application/vnd.in-toto+json",
payload: payload,
signatures: new[] { sig1, sig2, sig3 });
// Assert - null comes first, then alphabetical
envelope.Signatures[0].KeyId.Should().BeNull();
envelope.Signatures[1].KeyId.Should().Be("a-key");
envelope.Signatures[2].KeyId.Should().Be("z-key");
}
[Fact]
public void DsseEnvelope_Generation_DifferentSignatureOrder_ProducesSameEnvelope()
{
// Arrange
var payload = Encoding.UTF8.GetBytes("test");
var sig1 = DsseSignature.FromBytes(new byte[] { 0x01 }, "key-a");
var sig2 = DsseSignature.FromBytes(new byte[] { 0x02 }, "key-b");
// Act - create envelopes with different signature order
var envelope1 = new DsseEnvelope("application/vnd.in-toto+json", payload, new[] { sig1, sig2 });
var envelope2 = new DsseEnvelope("application/vnd.in-toto+json", payload, new[] { sig2, sig1 });
// Assert - signatures should be normalized to same order
envelope1.Signatures[0].KeyId.Should().Be(envelope2.Signatures[0].KeyId);
envelope1.Signatures[1].KeyId.Should().Be(envelope2.Signatures[1].KeyId);
}
[Fact]
public void DsseEnvelope_Generation_PreservesPayloadBytes()
{
// Arrange
var originalPayload = Encoding.UTF8.GetBytes("""{"_type":"https://in-toto.io/Statement/v1","subject":[]}""");
var signature = DsseSignature.FromBytes(new byte[] { 0xAB, 0xCD }, "key");
// Act
var envelope = new DsseEnvelope("application/vnd.in-toto+json", originalPayload, new[] { signature });
// Assert
envelope.Payload.ToArray().Should().BeEquivalentTo(originalPayload);
}
// ATTESTOR-5100-002: DSSE envelope verification tests
[Fact]
public void DsseEnvelope_Verification_ValidEnvelope_HasCorrectPayloadType()
{
// Arrange
var payload = CreateInTotoPayload();
var signature = DsseSignature.FromBytes(new byte[] { 0x01, 0x02, 0x03 }, "valid-key");
var envelope = new DsseEnvelope("application/vnd.in-toto+json", payload, new[] { signature });
// Act & Assert
envelope.PayloadType.Should().Be("application/vnd.in-toto+json");
envelope.Signatures.Should().NotBeEmpty();
envelope.Signatures[0].Signature.Should().NotBeNullOrEmpty();
}
[Fact]
public void DsseEnvelope_Verification_SignatureIsBase64Encoded()
{
// Arrange
var payload = CreateInTotoPayload();
var signatureBytes = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 };
var signature = DsseSignature.FromBytes(signatureBytes, "key");
var envelope = new DsseEnvelope("application/vnd.in-toto+json", payload, new[] { signature });
// Act
var sigBase64 = envelope.Signatures[0].Signature;
// Assert - should be valid base64
var decoded = Convert.FromBase64String(sigBase64);
decoded.Should().BeEquivalentTo(signatureBytes);
}
[Fact]
public void DsseEnvelope_Verification_PayloadCanBeDeserialized()
{
// Arrange
var statement = CreateEvidenceStatement();
var payloadBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(statement));
var signature = DsseSignature.FromBytes(new byte[] { 0x01 }, "key");
var envelope = new DsseEnvelope("application/vnd.in-toto+json", payloadBytes, new[] { signature });
// Act
var deserializedPayload = JsonSerializer.Deserialize<EvidenceStatement>(envelope.Payload.Span);
// Assert
deserializedPayload.Should().NotBeNull();
deserializedPayload!.Type.Should().Be("https://in-toto.io/Statement/v1");
deserializedPayload.PredicateType.Should().Be("evidence.stella/v1");
}
[Fact]
public void DsseEnvelope_Verification_MultipleSignatures_AllPreserved()
{
// Arrange
var payload = CreateInTotoPayload();
var signatures = new[]
{
DsseSignature.FromBytes(new byte[] { 0x01 }, "key-1"),
DsseSignature.FromBytes(new byte[] { 0x02 }, "key-2"),
DsseSignature.FromBytes(new byte[] { 0x03 }, "key-3")
};
var envelope = new DsseEnvelope("application/vnd.in-toto+json", payload, signatures);
// Act & Assert
envelope.Signatures.Should().HaveCount(3);
envelope.Signatures.Select(s => s.KeyId).Should().Contain(new[] { "key-1", "key-2", "key-3" });
}
[Fact]
public void DsseEnvelope_Verification_DetachedPayloadReference_Preserved()
{
// Arrange
var payload = CreateInTotoPayload();
var signature = DsseSignature.FromBytes(new byte[] { 0x01 }, "key");
var detachedRef = new DsseDetachedPayloadReference(
Uri: "oci://registry.example.com/sbom@sha256:abc123",
Digest: "sha256:abc123def456",
Size: 1024);
var envelope = new DsseEnvelope(
"application/vnd.in-toto+json",
payload,
new[] { signature },
detachedPayload: detachedRef);
// Act & Assert
envelope.DetachedPayload.Should().NotBeNull();
envelope.DetachedPayload!.Uri.Should().Be("oci://registry.example.com/sbom@sha256:abc123");
envelope.DetachedPayload.Digest.Should().Be("sha256:abc123def456");
envelope.DetachedPayload.Size.Should().Be(1024);
}
[Fact]
public void DsseEnvelope_DeterministicSerialization_SameInputs_ProduceSameOutput()
{
// Arrange
var payload = CreateInTotoPayload();
var signature = DsseSignature.FromBytes(new byte[] { 0x01, 0x02, 0x03 }, "deterministic-key");
// Act - create same envelope multiple times
var envelopes = Enumerable.Range(0, 10)
.Select(_ => new DsseEnvelope("application/vnd.in-toto+json", payload, new[] { signature }))
.ToList();
// Assert - all envelopes should have identical structure
var firstPayload = envelopes[0].Payload.ToArray();
var firstSig = envelopes[0].Signatures[0].Signature;
foreach (var envelope in envelopes.Skip(1))
{
envelope.Payload.ToArray().Should().BeEquivalentTo(firstPayload);
envelope.Signatures[0].Signature.Should().Be(firstSig);
}
}
// Helper methods
private static byte[] CreateInTotoPayload()
{
var statement = new
{
_type = "https://in-toto.io/Statement/v1",
predicateType = "test/v1",
subject = new[]
{
new { name = "test-artifact", digest = new { sha256 = new string('a', 64) } }
},
predicate = new { test = "value" }
};
return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(statement));
}
private static EvidenceStatement CreateEvidenceStatement()
{
return new EvidenceStatement
{
Subject = new[]
{
new Subject
{
Name = "test-image",
Digest = new Dictionary<string, string> { ["sha256"] = new string('a', 64) }
}
},
Predicate = new EvidencePayload
{
Source = "trivy",
SourceVersion = "0.50.0",
CollectionTime = FixedTime,
SbomEntryId = "sha256:sbom-entry",
VulnerabilityId = "CVE-2025-0001",
RawFinding = new { severity = "high" },
EvidenceId = $"sha256:{new string('b', 64)}"
}
};
}
}

View File

@@ -0,0 +1,451 @@
// -----------------------------------------------------------------------------
// InTotoStatementSnapshotTests.cs
// Sprint: SPRINT_5100_0009_0007_attestor_tests
// Tasks: ATTESTOR-5100-003, ATTESTOR-5100-004, ATTESTOR-5100-005
// Description: Model L0 snapshot tests for in-toto statement types
// -----------------------------------------------------------------------------
using System.Text.Json;
using System.Text.Json.Nodes;
using FluentAssertions;
using StellaOps.Attestor.ProofChain.Builders;
using StellaOps.Attestor.ProofChain.Statements;
using Xunit;
namespace StellaOps.Attestor.ProofChain.Tests.Statements;
/// <summary>
/// Snapshot tests for in-toto statement types.
/// Implements Model L0 test requirements:
/// - ATTESTOR-5100-003: SLSA provenance v1.0 canonical JSON snapshot tests
/// - ATTESTOR-5100-004: VEX attestation canonical JSON snapshot tests
/// - ATTESTOR-5100-005: SBOM attestation canonical JSON snapshot tests
/// </summary>
[Trait("Category", "Unit")]
[Trait("Category", "Snapshot")]
[Trait("Category", "InTotoStatement")]
public sealed class InTotoStatementSnapshotTests
{
private static readonly DateTimeOffset FixedTime = new(2025, 12, 24, 12, 0, 0, TimeSpan.Zero);
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
// ATTESTOR-5100-003: in-toto statement tests (base structure)
[Fact]
public void InTotoStatement_HasCorrectTypeField()
{
// Arrange
var statement = CreateEvidenceStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
// Assert
node!["_type"]!.GetValue<string>().Should().Be("https://in-toto.io/Statement/v1");
}
[Fact]
public void InTotoStatement_Subject_HasRequiredFields()
{
// Arrange
var statement = CreateEvidenceStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var subject = node!["subject"]!.AsArray()[0];
// Assert
subject!["name"].Should().NotBeNull();
subject["digest"].Should().NotBeNull();
subject["digest"]!["sha256"].Should().NotBeNull();
}
[Fact]
public void InTotoStatement_Subject_DigestIsLowercase()
{
// Arrange
var statement = CreateEvidenceStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var digest = node!["subject"]![0]!["digest"]!["sha256"]!.GetValue<string>();
// Assert
digest.Should().MatchRegex("^[a-f0-9]{64}$", "digest should be lowercase hex");
}
[Fact]
public void InTotoStatement_PredicateType_IsPresent()
{
// Arrange
var statement = CreateEvidenceStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
// Assert
node!["predicateType"]!.GetValue<string>().Should().NotBeNullOrEmpty();
}
[Fact]
public void InTotoStatement_Serialization_IsDeterministic()
{
// Arrange
var statement = CreateEvidenceStatement();
// Act - serialize multiple times
var serializations = Enumerable.Range(0, 10)
.Select(_ => JsonSerializer.Serialize(statement, JsonOptions))
.ToList();
// Assert - all should be identical
serializations.Distinct().Should().HaveCount(1);
}
// ATTESTOR-5100-004: VEX attestation canonical JSON tests
[Fact]
public void VexVerdictStatement_HasCorrectPredicateType()
{
// Arrange
var statement = CreateVexVerdictStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
// Assert
node!["predicateType"]!.GetValue<string>().Should().Be("cdx-vex.stella/v1");
}
[Fact]
public void VexVerdictStatement_HasRequiredPredicateFields()
{
// Arrange
var statement = CreateVexVerdictStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var predicate = node!["predicate"];
// Assert
predicate!["sbomEntryId"].Should().NotBeNull();
predicate["vulnerabilityId"].Should().NotBeNull();
predicate["status"].Should().NotBeNull();
predicate["justification"].Should().NotBeNull();
predicate["policyVersion"].Should().NotBeNull();
predicate["reasoningId"].Should().NotBeNull();
predicate["vexVerdictId"].Should().NotBeNull();
}
[Fact]
public void VexVerdictStatement_Status_IsValidVexStatus()
{
// Arrange
var validStatuses = new[] { "not_affected", "affected", "fixed", "under_investigation" };
// Act & Assert
foreach (var status in validStatuses)
{
var statement = CreateVexVerdictStatement(status);
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
node!["predicate"]!["status"]!.GetValue<string>().Should().Be(status);
}
}
[Fact]
public void VexVerdictStatement_VexVerdictId_HasCorrectFormat()
{
// Arrange
var statement = CreateVexVerdictStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var verdictId = node!["predicate"]!["vexVerdictId"]!.GetValue<string>();
// Assert
verdictId.Should().StartWith("sha256:");
verdictId.Should().HaveLength(71, "sha256: prefix (7) + 64 hex chars = 71");
}
[Fact]
public void VexVerdictStatement_Serialization_IsDeterministic()
{
// Arrange
var statement = CreateVexVerdictStatement();
// Act
var serializations = Enumerable.Range(0, 10)
.Select(_ => JsonSerializer.Serialize(statement, JsonOptions))
.ToList();
// Assert
serializations.Distinct().Should().HaveCount(1);
}
// ATTESTOR-5100-005: SBOM attestation canonical JSON tests
[Fact]
public void SbomLinkageStatement_HasCorrectPredicateType()
{
// Arrange
var statement = CreateSbomLinkageStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
// Assert
node!["predicateType"]!.GetValue<string>()
.Should().Be("https://stella-ops.org/predicates/sbom-linkage/v1");
}
[Fact]
public void SbomLinkageStatement_Sbom_HasRequiredFields()
{
// Arrange
var statement = CreateSbomLinkageStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var sbom = node!["predicate"]!["sbom"];
// Assert
sbom!["id"].Should().NotBeNull();
sbom["format"].Should().NotBeNull();
sbom["specVersion"].Should().NotBeNull();
sbom["mediaType"].Should().NotBeNull();
sbom["sha256"].Should().NotBeNull();
}
[Fact]
public void SbomLinkageStatement_CycloneDX16_HasCorrectMediaType()
{
// Arrange
var statement = CreateSbomLinkageStatement(format: "cyclonedx", specVersion: "1.6");
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var mediaType = node!["predicate"]!["sbom"]!["mediaType"]!.GetValue<string>();
// Assert
mediaType.Should().Be("application/vnd.cyclonedx+json");
}
[Fact]
public void SbomLinkageStatement_SPDX301_HasCorrectMediaType()
{
// Arrange
var statement = CreateSbomLinkageStatement(format: "spdx", specVersion: "3.0.1");
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var mediaType = node!["predicate"]!["sbom"]!["mediaType"]!.GetValue<string>();
// Assert
mediaType.Should().Be("application/spdx+json");
}
[Fact]
public void SbomLinkageStatement_Generator_HasRequiredFields()
{
// Arrange
var statement = CreateSbomLinkageStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var generator = node!["predicate"]!["generator"];
// Assert
generator!["name"].Should().NotBeNull();
generator["version"].Should().NotBeNull();
}
[Fact]
public void SbomLinkageStatement_GeneratedAt_IsIso8601()
{
// Arrange
var statement = CreateSbomLinkageStatement();
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var generatedAt = node!["predicate"]!["generatedAt"]!.GetValue<string>();
// Assert - should parse as valid ISO 8601
DateTimeOffset.TryParse(generatedAt, out _).Should().BeTrue();
}
[Fact]
public void SbomLinkageStatement_MultipleSubjects_AllPreserved()
{
// Arrange
var subjects = new[]
{
new Subject { Name = "image:demo", Digest = new Dictionary<string, string> { ["sha256"] = new string('a', 64) } },
new Subject { Name = "pkg:npm/lodash@4.17.21", Digest = new Dictionary<string, string> { ["sha256"] = new string('b', 64) } },
new Subject { Name = "pkg:maven/org.apache/log4j@2.17.1", Digest = new Dictionary<string, string> { ["sha256"] = new string('c', 64) } }
};
var statement = CreateSbomLinkageStatement(subjects: subjects);
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var subjectArray = node!["subject"]!.AsArray();
// Assert
subjectArray.Should().HaveCount(3);
subjectArray[0]!["name"]!.GetValue<string>().Should().Be("image:demo");
subjectArray[1]!["name"]!.GetValue<string>().Should().Be("pkg:npm/lodash@4.17.21");
subjectArray[2]!["name"]!.GetValue<string>().Should().Be("pkg:maven/org.apache/log4j@2.17.1");
}
[Fact]
public void SbomLinkageStatement_Serialization_IsDeterministic()
{
// Arrange
var statement = CreateSbomLinkageStatement();
// Act
var serializations = Enumerable.Range(0, 10)
.Select(_ => JsonSerializer.Serialize(statement, JsonOptions))
.ToList();
// Assert
serializations.Distinct().Should().HaveCount(1);
}
[Fact]
public void SbomLinkageStatement_Tags_OptionalButPreserved()
{
// Arrange
var statement = CreateSbomLinkageStatement(tags: new Dictionary<string, string>
{
["env"] = "production",
["team"] = "security"
});
// Act
var json = JsonSerializer.Serialize(statement, JsonOptions);
var node = JsonNode.Parse(json);
var tags = node!["predicate"]!["tags"];
// Assert
tags.Should().NotBeNull();
tags!["env"]!.GetValue<string>().Should().Be("production");
tags["team"]!.GetValue<string>().Should().Be("security");
}
// Helper methods
private static EvidenceStatement CreateEvidenceStatement()
{
return new EvidenceStatement
{
Subject = new[]
{
new Subject
{
Name = "test-artifact",
Digest = new Dictionary<string, string> { ["sha256"] = new string('a', 64) }
}
},
Predicate = new EvidencePayload
{
Source = "trivy",
SourceVersion = "0.50.0",
CollectionTime = FixedTime,
SbomEntryId = "sha256:sbom-entry",
VulnerabilityId = "CVE-2025-0001",
RawFinding = new { severity = "high" },
EvidenceId = $"sha256:{new string('b', 64)}"
}
};
}
private static VexVerdictStatement CreateVexVerdictStatement(string status = "not_affected")
{
return new VexVerdictStatement
{
Subject = new[]
{
new Subject
{
Name = "pkg:npm/lodash@4.17.21",
Digest = new Dictionary<string, string> { ["sha256"] = new string('a', 64) }
}
},
Predicate = new VexVerdictPayload
{
SbomEntryId = "sha256:sbom:pkg:npm/lodash@4.17.21",
VulnerabilityId = "CVE-2025-0001",
Status = status,
Justification = "vulnerable_code_not_in_execute_path",
PolicyVersion = "v1.0.0",
ReasoningId = $"sha256:{new string('c', 64)}",
VexVerdictId = $"sha256:{new string('d', 64)}"
}
};
}
private static SbomLinkageStatement CreateSbomLinkageStatement(
string format = "cyclonedx",
string specVersion = "1.6",
Subject[]? subjects = null,
Dictionary<string, string>? tags = null)
{
var mediaType = format.ToLowerInvariant() switch
{
"cyclonedx" => "application/vnd.cyclonedx+json",
"spdx" => "application/spdx+json",
_ => "application/json"
};
return new SbomLinkageStatement
{
Subject = subjects ?? new[]
{
new Subject
{
Name = "image:demo",
Digest = new Dictionary<string, string> { ["sha256"] = new string('a', 64) }
}
},
Predicate = new SbomLinkagePayload
{
Sbom = new SbomDescriptor
{
Id = "sbom-001",
Format = format,
SpecVersion = specVersion,
MediaType = mediaType,
Sha256 = new string('e', 64),
Location = "oci://registry.example.com/sbom@sha256:abc123"
},
Generator = new GeneratorDescriptor
{
Name = "stellaops-sbomgen",
Version = "1.0.0"
},
GeneratedAt = FixedTime,
Tags = tags
}
};
}
}

View File

@@ -27,6 +27,7 @@
<ItemGroup>
<ProjectReference Include="..\..\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj" />
<ProjectReference Include="..\..\StellaOps.Attestor.Envelope\StellaOps.Attestor.Envelope.csproj" />
</ItemGroup>
</Project>