new two advisories and sprints work on them

This commit is contained in:
master
2026-01-16 18:39:36 +02:00
parent 9daf619954
commit c3a6269d55
72 changed files with 15540 additions and 18 deletions

View File

@@ -0,0 +1,372 @@
// -----------------------------------------------------------------------------
// DeltaSigAttestorIntegrationTests.cs
// Sprint: SPRINT_20260117_003_BINDEX_delta_sig_predicate
// Task: DSP-008 - Unit tests for DeltaSig attestation
// Description: Unit tests for delta-sig attestation integration
// -----------------------------------------------------------------------------
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Time.Testing;
using StellaOps.BinaryIndex.DeltaSig.Attestation;
namespace StellaOps.BinaryIndex.DeltaSig.Tests.Attestation;
/// <summary>
/// Unit tests for delta-sig attestation integration.
/// </summary>
[Trait("Category", "Unit")]
public sealed class DeltaSigAttestorIntegrationTests
{
private static readonly DateTimeOffset FixedTimestamp = new(2026, 1, 16, 12, 0, 0, TimeSpan.Zero);
private readonly FakeTimeProvider _timeProvider;
public DeltaSigAttestorIntegrationTests()
{
_timeProvider = new FakeTimeProvider(FixedTimestamp);
}
[Fact]
public void CreatePredicate_ValidInput_CreatesPredicateWithCorrectType()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest();
// Act
var predicate = service.CreatePredicate(request);
// Assert
predicate.PredicateType.Should().Be("https://stellaops.io/delta-sig/v1");
predicate.Subject.Should().NotBeEmpty();
predicate.DeltaSignatures.Should().NotBeEmpty();
}
[Fact]
public void CreatePredicate_WithSymbols_IncludesAllSymbols()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest(symbolCount: 5);
// Act
var predicate = service.CreatePredicate(request);
// Assert
predicate.DeltaSignatures.Should().HaveCount(5);
predicate.Statistics.TotalSymbols.Should().Be(5);
}
[Fact]
public void CreatePredicate_IncludesTimestamp()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest();
// Act
var predicate = service.CreatePredicate(request);
// Assert
predicate.Timestamp.Should().Be(FixedTimestamp);
}
[Fact]
public void CreatePredicate_ComputesContentDigest()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest();
// Act
var predicate = service.CreatePredicate(request);
// Assert
predicate.Subject.Should().ContainSingle();
predicate.Subject.First().Digest.Should().ContainKey("sha256");
predicate.Subject.First().Digest["sha256"].Should().NotBeNullOrEmpty();
}
[Fact]
public void CreatePredicate_DeterministicOutput()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest();
// Act
var predicate1 = service.CreatePredicate(request);
var predicate2 = service.CreatePredicate(request);
// Assert
predicate1.DeltaSignatures.Should().BeEquivalentTo(predicate2.DeltaSignatures);
predicate1.Subject.First().Digest["sha256"].Should().Be(predicate2.Subject.First().Digest["sha256"]);
}
[Fact]
public void CreateEnvelope_ValidPredicate_CreatesDsseEnvelope()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest();
var predicate = service.CreatePredicate(request);
// Act
var envelope = service.CreateEnvelope(predicate);
// Assert
envelope.PayloadType.Should().Be("application/vnd.in-toto+json");
envelope.Payload.Should().NotBeNullOrEmpty();
}
[Fact]
public void CreateEnvelope_PayloadIsBase64Encoded()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest();
var predicate = service.CreatePredicate(request);
// Act
var envelope = service.CreateEnvelope(predicate);
// Assert
var decoded = Convert.FromBase64String(envelope.Payload);
decoded.Should().NotBeEmpty();
}
[Fact]
public void SerializePredicate_ProducesValidJson()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest();
var predicate = service.CreatePredicate(request);
// Act
var json = service.SerializePredicate(predicate);
// Assert
json.Should().Contain("\"predicateType\"");
json.Should().Contain("\"subject\"");
json.Should().Contain("\"deltaSignatures\"");
json.Should().Contain("delta-sig/v1");
}
[Fact]
public void ValidatePredicate_ValidPredicate_ReturnsTrue()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest();
var predicate = service.CreatePredicate(request);
// Act
var result = service.ValidatePredicate(predicate);
// Assert
result.IsValid.Should().BeTrue();
result.Errors.Should().BeEmpty();
}
[Fact]
public void ValidatePredicate_EmptySubject_ReturnsFalse()
{
// Arrange
var service = CreateService();
var predicate = new DeltaSigPredicate(
PredicateType: "https://stellaops.io/delta-sig/v1",
Subject: Array.Empty<InTotoSubject>(),
DeltaSignatures: new[] { CreateTestDeltaSig() },
Timestamp: FixedTimestamp,
Statistics: new DeltaSigStatistics(1, 0, 0));
// Act
var result = service.ValidatePredicate(predicate);
// Assert
result.IsValid.Should().BeFalse();
result.Errors.Should().Contain(e => e.Contains("subject", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void ValidatePredicate_EmptyDeltaSignatures_ReturnsFalse()
{
// Arrange
var service = CreateService();
var predicate = new DeltaSigPredicate(
PredicateType: "https://stellaops.io/delta-sig/v1",
Subject: new[] { CreateTestSubject() },
DeltaSignatures: Array.Empty<DeltaSignatureEntry>(),
Timestamp: FixedTimestamp,
Statistics: new DeltaSigStatistics(0, 0, 0));
// Act
var result = service.ValidatePredicate(predicate);
// Assert
result.IsValid.Should().BeFalse();
result.Errors.Should().Contain(e => e.Contains("signature", StringComparison.OrdinalIgnoreCase));
}
[Fact]
public void ComparePredicate_SameContent_ReturnsNoDifferences()
{
// Arrange
var service = CreateService();
var request = CreateValidPredicateRequest();
var predicate1 = service.CreatePredicate(request);
var predicate2 = service.CreatePredicate(request);
// Act
var diff = service.ComparePredicate(predicate1, predicate2);
// Assert
diff.HasDifferences.Should().BeFalse();
diff.AddedSymbols.Should().BeEmpty();
diff.RemovedSymbols.Should().BeEmpty();
diff.ModifiedSymbols.Should().BeEmpty();
}
[Fact]
public void ComparePredicate_AddedSymbol_DetectsAddition()
{
// Arrange
var service = CreateService();
var request1 = CreateValidPredicateRequest(symbolCount: 3);
var request2 = CreateValidPredicateRequest(symbolCount: 4);
var predicate1 = service.CreatePredicate(request1);
var predicate2 = service.CreatePredicate(request2);
// Act
var diff = service.ComparePredicate(predicate1, predicate2);
// Assert
diff.HasDifferences.Should().BeTrue();
diff.AddedSymbols.Should().HaveCount(1);
}
[Fact]
public void ComparePredicate_RemovedSymbol_DetectsRemoval()
{
// Arrange
var service = CreateService();
var request1 = CreateValidPredicateRequest(symbolCount: 4);
var request2 = CreateValidPredicateRequest(symbolCount: 3);
var predicate1 = service.CreatePredicate(request1);
var predicate2 = service.CreatePredicate(request2);
// Act
var diff = service.ComparePredicate(predicate1, predicate2);
// Assert
diff.HasDifferences.Should().BeTrue();
diff.RemovedSymbols.Should().HaveCount(1);
}
// Helper methods
private IDeltaSigAttestorIntegration CreateService()
{
return new DeltaSigAttestorIntegration(
Options.Create(new DeltaSigAttestorOptions
{
PredicateType = "https://stellaops.io/delta-sig/v1",
IncludeStatistics = true
}),
_timeProvider,
NullLogger<DeltaSigAttestorIntegration>.Instance);
}
private static DeltaSigPredicateRequest CreateValidPredicateRequest(int symbolCount = 3)
{
var signatures = Enumerable.Range(0, symbolCount)
.Select(i => CreateTestDeltaSig(i))
.ToArray();
return new DeltaSigPredicateRequest(
BinaryDigest: $"sha256:abc123def456{symbolCount:D4}",
BinaryName: "libtest.so",
Signatures: signatures);
}
private static DeltaSignatureEntry CreateTestDeltaSig(int index = 0)
{
return new DeltaSignatureEntry(
SymbolName: $"test_function_{index}",
HashAlgorithm: "sha256",
HashHex: $"abcdef{index:D8}0123456789abcdef0123456789abcdef0123456789abcdef01234567",
SizeBytes: 128 + index * 16,
Scope: ".text");
}
private static InTotoSubject CreateTestSubject()
{
return new InTotoSubject(
Name: "libtest.so",
Digest: new Dictionary<string, string>
{
["sha256"] = "abc123def4560000"
});
}
}
// Supporting types for tests (would normally be in main project)
public record DeltaSigPredicate(
string PredicateType,
IReadOnlyList<InTotoSubject> Subject,
IReadOnlyList<DeltaSignatureEntry> DeltaSignatures,
DateTimeOffset Timestamp,
DeltaSigStatistics Statistics);
public record InTotoSubject(
string Name,
IReadOnlyDictionary<string, string> Digest);
public record DeltaSignatureEntry(
string SymbolName,
string HashAlgorithm,
string HashHex,
int SizeBytes,
string Scope);
public record DeltaSigStatistics(
int TotalSymbols,
int AddedSymbols,
int ModifiedSymbols);
public record DeltaSigPredicateRequest(
string BinaryDigest,
string BinaryName,
IReadOnlyList<DeltaSignatureEntry> Signatures);
public record DeltaSigPredicateDiff(
bool HasDifferences,
IReadOnlyList<string> AddedSymbols,
IReadOnlyList<string> RemovedSymbols,
IReadOnlyList<string> ModifiedSymbols);
public record PredicateValidationResult(
bool IsValid,
IReadOnlyList<string> Errors);
public record DsseEnvelope(
string PayloadType,
string Payload);
public record DeltaSigAttestorOptions
{
public string PredicateType { get; init; } = "https://stellaops.io/delta-sig/v1";
public bool IncludeStatistics { get; init; } = true;
}
public interface IDeltaSigAttestorIntegration
{
DeltaSigPredicate CreatePredicate(DeltaSigPredicateRequest request);
DsseEnvelope CreateEnvelope(DeltaSigPredicate predicate);
string SerializePredicate(DeltaSigPredicate predicate);
PredicateValidationResult ValidatePredicate(DeltaSigPredicate predicate);
DeltaSigPredicateDiff ComparePredicate(DeltaSigPredicate before, DeltaSigPredicate after);
}

View File

@@ -0,0 +1,499 @@
// -----------------------------------------------------------------------------
// DeltaSigEndToEndTests.cs
// Sprint: SPRINT_20260117_003_BINDEX_delta_sig_predicate
// Task: DSP-009 - Integration tests for delta-sig predicate E2E flow
// Description: End-to-end tests for delta-sig generation, signing, submission, and verification
// -----------------------------------------------------------------------------
using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using FluentAssertions;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Time.Testing;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.BinaryIndex.DeltaSig.Tests.Integration;
[Trait("Category", TestCategories.Integration)]
public sealed class DeltaSigEndToEndTests
{
private static readonly DateTimeOffset FixedTimestamp = new(2026, 1, 16, 12, 0, 0, TimeSpan.Zero);
private readonly FakeTimeProvider _timeProvider;
private readonly MockRekorClient _rekorClient;
private readonly MockSigningService _signingService;
public DeltaSigEndToEndTests()
{
_timeProvider = new FakeTimeProvider(FixedTimestamp);
_rekorClient = new MockRekorClient();
_signingService = new MockSigningService();
}
[Fact]
public async Task FullFlow_GenerateSignSubmitVerify_Succeeds()
{
// Arrange
var service = CreateService();
var beforeBinary = CreateTestBinary("libtest-1.0.so", 10);
var afterBinary = CreateTestBinary("libtest-1.1.so", 12); // 2 new functions
// Act - Step 1: Generate delta-sig predicate
var predicate = await service.GenerateAsync(beforeBinary, afterBinary, CancellationToken.None);
// Assert - predicate created correctly
predicate.Should().NotBeNull();
predicate.PredicateType.Should().Contain("delta-sig");
predicate.Summary.FunctionsAdded.Should().Be(2);
predicate.Summary.FunctionsModified.Should().Be(0);
// Act - Step 2: Sign the predicate
var envelope = await service.SignAsync(predicate, CancellationToken.None);
// Assert - envelope created
envelope.Should().NotBeNull();
envelope.PayloadType.Should().Be("application/vnd.in-toto+json");
envelope.Signatures.Should().NotBeEmpty();
// Act - Step 3: Submit to Rekor
var submission = await service.SubmitToRekorAsync(envelope, CancellationToken.None);
// Assert - submission successful
submission.Success.Should().BeTrue();
submission.EntryId.Should().NotBeNullOrEmpty();
submission.LogIndex.Should().BeGreaterThan(0);
// Act - Step 4: Verify from Rekor
var verification = await service.VerifyFromRekorAsync(submission.EntryId!, CancellationToken.None);
// Assert - verification successful
verification.IsValid.Should().BeTrue();
verification.PredicateType.Should().Contain("delta-sig");
}
[Fact]
public async Task Generate_IdenticalBinaries_ReturnsEmptyDiff()
{
// Arrange
var service = CreateService();
var binary = CreateTestBinary("libtest.so", 5);
// Act
var predicate = await service.GenerateAsync(binary, binary, CancellationToken.None);
// Assert
predicate.Summary.FunctionsAdded.Should().Be(0);
predicate.Summary.FunctionsModified.Should().Be(0);
predicate.Summary.FunctionsRemoved.Should().Be(0);
predicate.Diff.Should().BeEmpty();
}
[Fact]
public async Task Generate_RemovedFunctions_TracksRemovals()
{
// Arrange
var service = CreateService();
var beforeBinary = CreateTestBinary("libtest-1.0.so", 10);
var afterBinary = CreateTestBinary("libtest-1.1.so", 7); // 3 removed
// Act
var predicate = await service.GenerateAsync(beforeBinary, afterBinary, CancellationToken.None);
// Assert
predicate.Summary.FunctionsRemoved.Should().Be(3);
}
[Fact]
public async Task Generate_ModifiedFunctions_TracksModifications()
{
// Arrange
var service = CreateService();
var beforeBinary = CreateTestBinaryWithModifications("libtest-1.0.so", 5, modifyIndices: new[] { 1, 3 });
var afterBinary = CreateTestBinaryWithModifications("libtest-1.1.so", 5, modifyIndices: new[] { 1, 3 }, modified: true);
// Act
var predicate = await service.GenerateAsync(beforeBinary, afterBinary, CancellationToken.None);
// Assert
predicate.Summary.FunctionsModified.Should().Be(2);
}
[Fact]
public async Task Verify_TamperedPredicate_FailsVerification()
{
// Arrange
var service = CreateService();
var beforeBinary = CreateTestBinary("libtest-1.0.so", 5);
var afterBinary = CreateTestBinary("libtest-1.1.so", 6);
var predicate = await service.GenerateAsync(beforeBinary, afterBinary, CancellationToken.None);
var envelope = await service.SignAsync(predicate, CancellationToken.None);
// Tamper with the envelope
var tamperedEnvelope = envelope with
{
Payload = Convert.ToBase64String(Encoding.UTF8.GetBytes("tampered content"))
};
// Act
var verification = await service.VerifyEnvelopeAsync(tamperedEnvelope, CancellationToken.None);
// Assert
verification.IsValid.Should().BeFalse();
verification.FailureReason.Should().Contain("signature");
}
[Fact]
public async Task PolicyGate_WithinLimits_Passes()
{
// Arrange
var service = CreateService();
var beforeBinary = CreateTestBinary("libtest-1.0.so", 10);
var afterBinary = CreateTestBinary("libtest-1.1.so", 12); // 2 added
var predicate = await service.GenerateAsync(beforeBinary, afterBinary, CancellationToken.None);
var policyOptions = new DeltaScopePolicyOptions
{
MaxAddedFunctions = 5,
MaxRemovedFunctions = 5,
MaxModifiedFunctions = 10,
MaxBytesChanged = 10000
};
// Act
var gateResult = await service.EvaluatePolicyAsync(predicate, policyOptions, CancellationToken.None);
// Assert
gateResult.Passed.Should().BeTrue();
gateResult.Violations.Should().BeEmpty();
}
[Fact]
public async Task PolicyGate_ExceedsLimits_FailsWithViolations()
{
// Arrange
var service = CreateService();
var beforeBinary = CreateTestBinary("libtest-1.0.so", 10);
var afterBinary = CreateTestBinary("libtest-1.1.so", 20); // 10 added
var predicate = await service.GenerateAsync(beforeBinary, afterBinary, CancellationToken.None);
var policyOptions = new DeltaScopePolicyOptions
{
MaxAddedFunctions = 5, // Exceeded
MaxRemovedFunctions = 5,
MaxModifiedFunctions = 10,
MaxBytesChanged = 10000
};
// Act
var gateResult = await service.EvaluatePolicyAsync(predicate, policyOptions, CancellationToken.None);
// Assert
gateResult.Passed.Should().BeFalse();
gateResult.Violations.Should().ContainSingle();
gateResult.Violations.First().Should().Contain("added");
}
[Fact]
public async Task SerializeDeserialize_RoundTrip_PreservesData()
{
// Arrange
var service = CreateService();
var beforeBinary = CreateTestBinary("libtest-1.0.so", 5);
var afterBinary = CreateTestBinary("libtest-1.1.so", 7);
var originalPredicate = await service.GenerateAsync(beforeBinary, afterBinary, CancellationToken.None);
// Act
var json = service.SerializePredicate(originalPredicate);
var deserialized = service.DeserializePredicate(json);
// Assert
deserialized.PredicateType.Should().Be(originalPredicate.PredicateType);
deserialized.Summary.FunctionsAdded.Should().Be(originalPredicate.Summary.FunctionsAdded);
deserialized.Subject.Should().HaveCount(originalPredicate.Subject.Count);
}
[Fact]
public async Task Generate_WithSemanticSimilarity_IncludesSimilarityScores()
{
// Arrange
var options = CreateOptions();
options.Value.IncludeSemanticSimilarity = true;
var service = CreateService(options);
var beforeBinary = CreateTestBinaryWithModifications("libtest-1.0.so", 5, modifyIndices: new[] { 2 });
var afterBinary = CreateTestBinaryWithModifications("libtest-1.1.so", 5, modifyIndices: new[] { 2 }, modified: true);
// Act
var predicate = await service.GenerateAsync(beforeBinary, afterBinary, CancellationToken.None);
// Assert
var modifiedFunc = predicate.Diff.FirstOrDefault(d => d.ChangeType == "modified");
modifiedFunc.Should().NotBeNull();
modifiedFunc!.SemanticSimilarity.Should().BeGreaterThan(0);
}
[Fact]
public async Task SubmitToRekor_Offline_ReturnsError()
{
// Arrange
_rekorClient.SetOffline(true);
var service = CreateService();
var predicate = CreateMinimalPredicate();
var envelope = await service.SignAsync(predicate, CancellationToken.None);
// Act
var submission = await service.SubmitToRekorAsync(envelope, CancellationToken.None);
// Assert
submission.Success.Should().BeFalse();
submission.Error.Should().Contain("offline");
}
[Fact]
public async Task Verify_StoredOfflineProof_SucceedsWithoutNetwork()
{
// Arrange
var service = CreateService();
var predicate = CreateMinimalPredicate();
var envelope = await service.SignAsync(predicate, CancellationToken.None);
// Submit and get proof
var submission = await service.SubmitToRekorAsync(envelope, CancellationToken.None);
var proof = await service.GetInclusionProofAsync(submission.EntryId!, CancellationToken.None);
// Go offline
_rekorClient.SetOffline(true);
// Act - verify using stored proof
var verification = await service.VerifyWithStoredProofAsync(envelope, proof, CancellationToken.None);
// Assert
verification.IsValid.Should().BeTrue();
verification.VerificationMode.Should().Be("offline");
}
// Helper methods
private IDeltaSigService CreateService(IOptions<DeltaSigServiceOptions>? options = null)
{
return new DeltaSigService(
options ?? CreateOptions(),
_rekorClient,
_signingService,
_timeProvider,
NullLogger<DeltaSigService>.Instance);
}
private static IOptions<DeltaSigServiceOptions> CreateOptions()
{
return Options.Create(new DeltaSigServiceOptions
{
PredicateType = "https://stellaops.io/delta-sig/v1",
IncludeSemanticSimilarity = false,
RekorUrl = "https://rekor.sigstore.dev"
});
}
private static TestBinaryData CreateTestBinary(string name, int functionCount)
{
var functions = Enumerable.Range(0, functionCount)
.Select(i => new TestFunction(
Name: $"func_{i:D3}",
Hash: ComputeHash($"{name}-func-{i}"),
Size: 100 + i * 10))
.ToImmutableArray();
return new TestBinaryData(
Name: name,
Digest: $"sha256:{ComputeHash(name)}",
Functions: functions);
}
private static TestBinaryData CreateTestBinaryWithModifications(
string name, int functionCount, int[] modifyIndices, bool modified = false)
{
var functions = Enumerable.Range(0, functionCount)
.Select(i =>
{
var suffix = modified && modifyIndices.Contains(i) ? "-modified" : "";
return new TestFunction(
Name: $"func_{i:D3}",
Hash: ComputeHash($"{name}-func-{i}{suffix}"),
Size: 100 + i * 10);
})
.ToImmutableArray();
return new TestBinaryData(
Name: name,
Digest: $"sha256:{ComputeHash(name)}",
Functions: functions);
}
private DeltaSigPredicate CreateMinimalPredicate()
{
return new DeltaSigPredicate(
PredicateType: "https://stellaops.io/delta-sig/v1",
Subject: ImmutableArray.Create(new InTotoSubject(
Name: "test.so",
Digest: ImmutableDictionary<string, string>.Empty.Add("sha256", "abc123"))),
Diff: ImmutableArray<DeltaSigDiffEntry>.Empty,
Summary: new DeltaSigSummary(0, 0, 0, 0),
Timestamp: FixedTimestamp,
BeforeDigest: "sha256:before",
AfterDigest: "sha256:after");
}
private static string ComputeHash(string input)
{
var bytes = Encoding.UTF8.GetBytes(input);
var hash = SHA256.HashData(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
}
}
// Supporting types for tests
public record TestBinaryData(
string Name,
string Digest,
ImmutableArray<TestFunction> Functions);
public record TestFunction(
string Name,
string Hash,
int Size);
public record DeltaSigPredicate(
string PredicateType,
ImmutableArray<InTotoSubject> Subject,
ImmutableArray<DeltaSigDiffEntry> Diff,
DeltaSigSummary Summary,
DateTimeOffset Timestamp,
string BeforeDigest,
string AfterDigest);
public record InTotoSubject(
string Name,
ImmutableDictionary<string, string> Digest);
public record DeltaSigDiffEntry(
string FunctionName,
string ChangeType,
string? BeforeHash,
string? AfterHash,
int BytesDelta,
double? SemanticSimilarity);
public record DeltaSigSummary(
int FunctionsAdded,
int FunctionsRemoved,
int FunctionsModified,
int TotalBytesChanged);
public record DsseEnvelope(
string PayloadType,
string Payload,
ImmutableArray<DsseSignature> Signatures);
public record DsseSignature(
string KeyId,
string Sig);
public record RekorSubmissionResult(
bool Success,
string? EntryId,
long LogIndex,
string? Error);
public record VerificationResult(
bool IsValid,
string? PredicateType,
string? FailureReason,
string? VerificationMode);
public record PolicyGateResult(
bool Passed,
ImmutableArray<string> Violations);
public record InclusionProof(
long TreeSize,
string RootHash,
ImmutableArray<string> Hashes);
public record DeltaScopePolicyOptions
{
public int MaxAddedFunctions { get; init; }
public int MaxRemovedFunctions { get; init; }
public int MaxModifiedFunctions { get; init; }
public int MaxBytesChanged { get; init; }
}
public record DeltaSigServiceOptions
{
public string PredicateType { get; init; } = "https://stellaops.io/delta-sig/v1";
public bool IncludeSemanticSimilarity { get; init; }
public string RekorUrl { get; init; } = "https://rekor.sigstore.dev";
}
public interface IDeltaSigService
{
Task<DeltaSigPredicate> GenerateAsync(TestBinaryData before, TestBinaryData after, CancellationToken ct);
Task<DsseEnvelope> SignAsync(DeltaSigPredicate predicate, CancellationToken ct);
Task<RekorSubmissionResult> SubmitToRekorAsync(DsseEnvelope envelope, CancellationToken ct);
Task<VerificationResult> VerifyFromRekorAsync(string entryId, CancellationToken ct);
Task<VerificationResult> VerifyEnvelopeAsync(DsseEnvelope envelope, CancellationToken ct);
Task<PolicyGateResult> EvaluatePolicyAsync(DeltaSigPredicate predicate, DeltaScopePolicyOptions options, CancellationToken ct);
string SerializePredicate(DeltaSigPredicate predicate);
DeltaSigPredicate DeserializePredicate(string json);
Task<InclusionProof> GetInclusionProofAsync(string entryId, CancellationToken ct);
Task<VerificationResult> VerifyWithStoredProofAsync(DsseEnvelope envelope, InclusionProof proof, CancellationToken ct);
}
public sealed class MockRekorClient
{
private bool _offline;
private long _nextLogIndex = 10000;
private readonly Dictionary<string, InclusionProof> _proofs = new();
public void SetOffline(bool offline) => _offline = offline;
public Task<RekorSubmissionResult> SubmitAsync(byte[] payload, CancellationToken ct)
{
if (_offline)
return Task.FromResult(new RekorSubmissionResult(false, null, 0, "offline"));
var entryId = Guid.NewGuid().ToString("N");
var logIndex = _nextLogIndex++;
_proofs[entryId] = new InclusionProof(logIndex, "root-hash", ImmutableArray.Create("h1", "h2"));
return Task.FromResult(new RekorSubmissionResult(true, entryId, logIndex, null));
}
public Task<InclusionProof?> GetProofAsync(string entryId, CancellationToken ct)
{
if (_offline) return Task.FromResult<InclusionProof?>(null);
_proofs.TryGetValue(entryId, out var proof);
return Task.FromResult(proof);
}
}
public sealed class MockSigningService
{
public Task<DsseEnvelope> SignAsync(string payload, CancellationToken ct)
{
var signature = Convert.ToBase64String(
SHA256.HashData(Encoding.UTF8.GetBytes(payload)));
return Task.FromResult(new DsseEnvelope(
PayloadType: "application/vnd.in-toto+json",
Payload: Convert.ToBase64String(Encoding.UTF8.GetBytes(payload)),
Signatures: ImmutableArray.Create(new DsseSignature("key-1", signature))));
}
}