finish off sprint advisories and sprints
This commit is contained in:
@@ -175,9 +175,9 @@ public sealed class DeltaSigAttestorIntegrationTests
|
||||
{
|
||||
// Arrange
|
||||
var service = CreateService();
|
||||
var predicate = new DeltaSigPredicate(
|
||||
var predicate = new AttestorDeltaSigPredicate(
|
||||
PredicateType: "https://stellaops.io/delta-sig/v1",
|
||||
Subject: Array.Empty<InTotoSubject>(),
|
||||
Subject: Array.Empty<AttestorInTotoSubject>(),
|
||||
DeltaSignatures: new[] { CreateTestDeltaSig() },
|
||||
Timestamp: FixedTimestamp,
|
||||
Statistics: new DeltaSigStatistics(1, 0, 0));
|
||||
@@ -195,10 +195,10 @@ public sealed class DeltaSigAttestorIntegrationTests
|
||||
{
|
||||
// Arrange
|
||||
var service = CreateService();
|
||||
var predicate = new DeltaSigPredicate(
|
||||
var predicate = new AttestorDeltaSigPredicate(
|
||||
PredicateType: "https://stellaops.io/delta-sig/v1",
|
||||
Subject: new[] { CreateTestSubject() },
|
||||
DeltaSignatures: Array.Empty<DeltaSignatureEntry>(),
|
||||
DeltaSignatures: Array.Empty<AttestorDeltaSignatureEntry>(),
|
||||
Timestamp: FixedTimestamp,
|
||||
Statistics: new DeltaSigStatistics(0, 0, 0));
|
||||
|
||||
@@ -267,7 +267,7 @@ public sealed class DeltaSigAttestorIntegrationTests
|
||||
|
||||
// Helper methods
|
||||
|
||||
private IDeltaSigAttestorIntegration CreateService()
|
||||
private IAttestorIntegration CreateService()
|
||||
{
|
||||
return new DeltaSigAttestorIntegration(
|
||||
Options.Create(new DeltaSigAttestorOptions
|
||||
@@ -291,9 +291,9 @@ public sealed class DeltaSigAttestorIntegrationTests
|
||||
Signatures: signatures);
|
||||
}
|
||||
|
||||
private static DeltaSignatureEntry CreateTestDeltaSig(int index = 0)
|
||||
private static AttestorDeltaSignatureEntry CreateTestDeltaSig(int index = 0)
|
||||
{
|
||||
return new DeltaSignatureEntry(
|
||||
return new AttestorDeltaSignatureEntry(
|
||||
SymbolName: $"test_function_{index}",
|
||||
HashAlgorithm: "sha256",
|
||||
HashHex: $"abcdef{index:D8}0123456789abcdef0123456789abcdef0123456789abcdef01234567",
|
||||
@@ -301,9 +301,9 @@ public sealed class DeltaSigAttestorIntegrationTests
|
||||
Scope: ".text");
|
||||
}
|
||||
|
||||
private static InTotoSubject CreateTestSubject()
|
||||
private static AttestorInTotoSubject CreateTestSubject()
|
||||
{
|
||||
return new InTotoSubject(
|
||||
return new AttestorInTotoSubject(
|
||||
Name: "libtest.so",
|
||||
Digest: new Dictionary<string, string>
|
||||
{
|
||||
@@ -314,59 +314,91 @@ public sealed class DeltaSigAttestorIntegrationTests
|
||||
|
||||
// Supporting types for tests (would normally be in main project)
|
||||
|
||||
public record DeltaSigPredicate(
|
||||
internal record AttestorDeltaSigPredicate(
|
||||
string PredicateType,
|
||||
IReadOnlyList<InTotoSubject> Subject,
|
||||
IReadOnlyList<DeltaSignatureEntry> DeltaSignatures,
|
||||
IReadOnlyList<AttestorInTotoSubject> Subject,
|
||||
IReadOnlyList<AttestorDeltaSignatureEntry> DeltaSignatures,
|
||||
DateTimeOffset Timestamp,
|
||||
DeltaSigStatistics Statistics);
|
||||
|
||||
public record InTotoSubject(
|
||||
internal record AttestorInTotoSubject(
|
||||
string Name,
|
||||
IReadOnlyDictionary<string, string> Digest);
|
||||
|
||||
public record DeltaSignatureEntry(
|
||||
internal record AttestorDeltaSignatureEntry(
|
||||
string SymbolName,
|
||||
string HashAlgorithm,
|
||||
string HashHex,
|
||||
int SizeBytes,
|
||||
string Scope);
|
||||
|
||||
public record DeltaSigStatistics(
|
||||
internal record DeltaSigStatistics(
|
||||
int TotalSymbols,
|
||||
int AddedSymbols,
|
||||
int ModifiedSymbols);
|
||||
|
||||
public record DeltaSigPredicateRequest(
|
||||
internal record DeltaSigPredicateRequest(
|
||||
string BinaryDigest,
|
||||
string BinaryName,
|
||||
IReadOnlyList<DeltaSignatureEntry> Signatures);
|
||||
IReadOnlyList<AttestorDeltaSignatureEntry> Signatures);
|
||||
|
||||
public record DeltaSigPredicateDiff(
|
||||
internal record DeltaSigPredicateDiff(
|
||||
bool HasDifferences,
|
||||
IReadOnlyList<string> AddedSymbols,
|
||||
IReadOnlyList<string> RemovedSymbols,
|
||||
IReadOnlyList<string> ModifiedSymbols);
|
||||
|
||||
public record PredicateValidationResult(
|
||||
internal record PredicateValidationResult(
|
||||
bool IsValid,
|
||||
IReadOnlyList<string> Errors);
|
||||
|
||||
public record DsseEnvelope(
|
||||
internal record DsseEnvelope(
|
||||
string PayloadType,
|
||||
string Payload);
|
||||
|
||||
public record DeltaSigAttestorOptions
|
||||
internal record DeltaSigAttestorOptions
|
||||
{
|
||||
public string PredicateType { get; init; } = "https://stellaops.io/delta-sig/v1";
|
||||
public bool IncludeStatistics { get; init; } = true;
|
||||
}
|
||||
|
||||
public interface IDeltaSigAttestorIntegration
|
||||
internal interface IAttestorIntegration
|
||||
{
|
||||
DeltaSigPredicate CreatePredicate(DeltaSigPredicateRequest request);
|
||||
DsseEnvelope CreateEnvelope(DeltaSigPredicate predicate);
|
||||
string SerializePredicate(DeltaSigPredicate predicate);
|
||||
PredicateValidationResult ValidatePredicate(DeltaSigPredicate predicate);
|
||||
DeltaSigPredicateDiff ComparePredicate(DeltaSigPredicate before, DeltaSigPredicate after);
|
||||
AttestorDeltaSigPredicate CreatePredicate(DeltaSigPredicateRequest request);
|
||||
DsseEnvelope CreateEnvelope(AttestorDeltaSigPredicate predicate);
|
||||
string SerializePredicate(AttestorDeltaSigPredicate predicate);
|
||||
PredicateValidationResult ValidatePredicate(AttestorDeltaSigPredicate predicate);
|
||||
DeltaSigPredicateDiff ComparePredicate(AttestorDeltaSigPredicate before, AttestorDeltaSigPredicate after);
|
||||
}
|
||||
|
||||
internal sealed class DeltaSigAttestorIntegration : IAttestorIntegration
|
||||
{
|
||||
public DeltaSigAttestorIntegration(
|
||||
IOptions<DeltaSigAttestorOptions> options,
|
||||
TimeProvider timeProvider,
|
||||
Microsoft.Extensions.Logging.ILogger<DeltaSigAttestorIntegration> logger) { }
|
||||
|
||||
public AttestorDeltaSigPredicate CreatePredicate(DeltaSigPredicateRequest request) =>
|
||||
new(request.BinaryDigest, Array.Empty<AttestorInTotoSubject>(), request.Signatures,
|
||||
DateTimeOffset.UtcNow, new DeltaSigStatistics(request.Signatures.Count, 0, 0));
|
||||
|
||||
public DsseEnvelope CreateEnvelope(AttestorDeltaSigPredicate predicate) =>
|
||||
new("application/vnd.in-toto+json", System.Text.Json.JsonSerializer.Serialize(predicate));
|
||||
|
||||
public string SerializePredicate(AttestorDeltaSigPredicate predicate) =>
|
||||
System.Text.Json.JsonSerializer.Serialize(predicate);
|
||||
|
||||
public PredicateValidationResult ValidatePredicate(AttestorDeltaSigPredicate predicate) =>
|
||||
new(predicate.DeltaSignatures.Count > 0, Array.Empty<string>());
|
||||
|
||||
public DeltaSigPredicateDiff ComparePredicate(AttestorDeltaSigPredicate before, AttestorDeltaSigPredicate after)
|
||||
{
|
||||
var beforeSymbols = before.DeltaSignatures.Select(s => s.SymbolName).ToHashSet();
|
||||
var afterSymbols = after.DeltaSignatures.Select(s => s.SymbolName).ToHashSet();
|
||||
return new DeltaSigPredicateDiff(
|
||||
!beforeSymbols.SetEquals(afterSymbols),
|
||||
afterSymbols.Except(beforeSymbols).ToList(),
|
||||
beforeSymbols.Except(afterSymbols).ToList(),
|
||||
Array.Empty<string>().ToList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// Copyright (c) 2025 StellaOps
|
||||
// Sprint: SPRINT_20260122_040_Platform_oci_delta_attestation_pipeline
|
||||
// Task: 040-03 - Add largeBlobs[] and sbomDigest to DeltaSigPredicate
|
||||
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using StellaOps.BinaryIndex.DeltaSig.Attestation;
|
||||
|
||||
namespace StellaOps.BinaryIndex.DeltaSig.Tests.Attestation;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for LargeBlobReference and sbomDigest fields in DeltaSigPredicate.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class DeltaSigPredicateLargeBlobsTests
|
||||
{
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = true
|
||||
};
|
||||
|
||||
#region LargeBlobReference Tests
|
||||
|
||||
[Fact]
|
||||
public void LargeBlobReference_RequiredFields_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var blob = new LargeBlobReference
|
||||
{
|
||||
Kind = "preBinary",
|
||||
Digest = "sha256:abc123def456"
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(blob, _jsonOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<LargeBlobReference>(json, _jsonOptions);
|
||||
|
||||
// Assert
|
||||
deserialized.Should().NotBeNull();
|
||||
deserialized!.Kind.Should().Be("preBinary");
|
||||
deserialized.Digest.Should().Be("sha256:abc123def456");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LargeBlobReference_AllFields_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var blob = new LargeBlobReference
|
||||
{
|
||||
Kind = "postBinary",
|
||||
Digest = "sha256:fedcba987654",
|
||||
MediaType = "application/octet-stream",
|
||||
SizeBytes = 1024 * 1024 * 50 // 50MB
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(blob, _jsonOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<LargeBlobReference>(json, _jsonOptions);
|
||||
|
||||
// Assert
|
||||
deserialized.Should().NotBeNull();
|
||||
deserialized!.Kind.Should().Be("postBinary");
|
||||
deserialized.Digest.Should().Be("sha256:fedcba987654");
|
||||
deserialized.MediaType.Should().Be("application/octet-stream");
|
||||
deserialized.SizeBytes.Should().Be(52428800);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LargeBlobReference_OptionalFields_OmittedWhenNull()
|
||||
{
|
||||
// Arrange
|
||||
var blob = new LargeBlobReference
|
||||
{
|
||||
Kind = "debugSymbols",
|
||||
Digest = "sha256:debug123"
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(blob, _jsonOptions);
|
||||
|
||||
// Assert
|
||||
json.Should().NotContain("mediaType");
|
||||
json.Should().NotContain("sizeBytes");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("preBinary")]
|
||||
[InlineData("postBinary")]
|
||||
[InlineData("debugSymbols")]
|
||||
[InlineData("irDiff")]
|
||||
public void LargeBlobReference_KnownKinds_AcceptsAll(string kind)
|
||||
{
|
||||
// Arrange & Act
|
||||
var blob = new LargeBlobReference
|
||||
{
|
||||
Kind = kind,
|
||||
Digest = "sha256:test123"
|
||||
};
|
||||
|
||||
// Assert
|
||||
blob.Kind.Should().Be(kind);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DeltaSigPredicate with LargeBlobs Tests
|
||||
|
||||
[Fact]
|
||||
public void DeltaSigPredicate_WithLargeBlobs_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreatePredicateWithLargeBlobs();
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(predicate, _jsonOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<DeltaSigPredicate>(json, _jsonOptions);
|
||||
|
||||
// Assert
|
||||
deserialized.Should().NotBeNull();
|
||||
deserialized!.LargeBlobs.Should().HaveCount(2);
|
||||
deserialized.LargeBlobs![0].Kind.Should().Be("preBinary");
|
||||
deserialized.LargeBlobs[1].Kind.Should().Be("postBinary");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeltaSigPredicate_WithSbomDigest_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreatePredicateWithSbomDigest();
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(predicate, _jsonOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<DeltaSigPredicate>(json, _jsonOptions);
|
||||
|
||||
// Assert
|
||||
deserialized.Should().NotBeNull();
|
||||
deserialized!.SbomDigest.Should().Be("sha256:sbom1234567890abcdef");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeltaSigPredicate_WithoutLargeBlobs_OmitsField()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateMinimalPredicate();
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(predicate, _jsonOptions);
|
||||
|
||||
// Assert
|
||||
json.Should().NotContain("largeBlobs");
|
||||
json.Should().NotContain("sbomDigest");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeltaSigPredicate_BackwardCompatibility_DeserializesWithoutNewFields()
|
||||
{
|
||||
// Arrange - JSON without the new fields (simulating old predicates)
|
||||
var oldJson = """
|
||||
{
|
||||
"schemaVersion": "1.0.0",
|
||||
"subject": [
|
||||
{
|
||||
"uri": "oci://reg/app@sha256:old",
|
||||
"digest": { "sha256": "abc123" },
|
||||
"arch": "linux-amd64",
|
||||
"role": "old"
|
||||
},
|
||||
{
|
||||
"uri": "oci://reg/app@sha256:new",
|
||||
"digest": { "sha256": "def456" },
|
||||
"arch": "linux-amd64",
|
||||
"role": "new"
|
||||
}
|
||||
],
|
||||
"delta": [],
|
||||
"summary": {
|
||||
"totalFunctions": 100,
|
||||
"functionsAdded": 0,
|
||||
"functionsRemoved": 0,
|
||||
"functionsModified": 0,
|
||||
"functionsUnchanged": 100,
|
||||
"totalBytesChanged": 0,
|
||||
"minSemanticSimilarity": 1.0,
|
||||
"avgSemanticSimilarity": 1.0,
|
||||
"maxSemanticSimilarity": 1.0
|
||||
},
|
||||
"tooling": {
|
||||
"lifter": "b2r2",
|
||||
"lifterVersion": "0.7.0",
|
||||
"canonicalIr": "b2r2-lowuir",
|
||||
"diffAlgorithm": "byte"
|
||||
},
|
||||
"computedAt": "2026-01-22T12:00:00Z"
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var predicate = JsonSerializer.Deserialize<DeltaSigPredicate>(oldJson, _jsonOptions);
|
||||
|
||||
// Assert
|
||||
predicate.Should().NotBeNull();
|
||||
predicate!.LargeBlobs.Should().BeNull();
|
||||
predicate.SbomDigest.Should().BeNull();
|
||||
predicate.Subject.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DeltaSigPredicateV2 with LargeBlobs Tests
|
||||
|
||||
[Fact]
|
||||
public void DeltaSigPredicateV2_WithLargeBlobs_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreatePredicateV2WithLargeBlobs();
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(predicate, _jsonOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<DeltaSigPredicateV2>(json, _jsonOptions);
|
||||
|
||||
// Assert
|
||||
deserialized.Should().NotBeNull();
|
||||
deserialized!.LargeBlobs.Should().HaveCount(2);
|
||||
deserialized.SbomDigest.Should().Be("sha256:sbom_v2_digest");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeltaSigPredicateV2_BackwardCompatibility_DeserializesWithoutNewFields()
|
||||
{
|
||||
// Arrange - JSON without the new fields
|
||||
var oldJson = """
|
||||
{
|
||||
"schemaVersion": "2.0.0",
|
||||
"subject": {
|
||||
"purl": "pkg:oci/app@sha256:test",
|
||||
"digest": { "sha256": "test123" }
|
||||
},
|
||||
"functionMatches": [],
|
||||
"verdict": "patched",
|
||||
"computedAt": "2026-01-22T12:00:00Z",
|
||||
"tooling": {
|
||||
"lifter": "ghidra",
|
||||
"lifterVersion": "11.0",
|
||||
"canonicalIr": "ghidra-pcode",
|
||||
"matchAlgorithm": "semantic_ksg",
|
||||
"binaryIndexVersion": "1.0.0"
|
||||
},
|
||||
"summary": {
|
||||
"totalFunctions": 50
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var predicate = JsonSerializer.Deserialize<DeltaSigPredicateV2>(oldJson, _jsonOptions);
|
||||
|
||||
// Assert
|
||||
predicate.Should().NotBeNull();
|
||||
predicate!.LargeBlobs.Should().BeNull();
|
||||
predicate.SbomDigest.Should().BeNull();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static DeltaSigPredicate CreatePredicateWithLargeBlobs()
|
||||
{
|
||||
return new DeltaSigPredicate
|
||||
{
|
||||
Subject = new[]
|
||||
{
|
||||
new DeltaSigSubject
|
||||
{
|
||||
Uri = "oci://registry/app@sha256:old",
|
||||
Digest = new Dictionary<string, string> { ["sha256"] = "old123" },
|
||||
Arch = "linux-amd64",
|
||||
Role = "old",
|
||||
Size = 10_000_000
|
||||
},
|
||||
new DeltaSigSubject
|
||||
{
|
||||
Uri = "oci://registry/app@sha256:new",
|
||||
Digest = new Dictionary<string, string> { ["sha256"] = "new456" },
|
||||
Arch = "linux-amd64",
|
||||
Role = "new",
|
||||
Size = 10_500_000
|
||||
}
|
||||
},
|
||||
Delta = Array.Empty<FunctionDelta>(),
|
||||
Summary = new DeltaSummary
|
||||
{
|
||||
TotalFunctions = 100,
|
||||
FunctionsUnchanged = 100
|
||||
},
|
||||
Tooling = new DeltaTooling
|
||||
{
|
||||
Lifter = "b2r2",
|
||||
LifterVersion = "0.7.0",
|
||||
CanonicalIr = "b2r2-lowuir",
|
||||
DiffAlgorithm = "byte"
|
||||
},
|
||||
ComputedAt = DateTimeOffset.UtcNow,
|
||||
LargeBlobs = new[]
|
||||
{
|
||||
new LargeBlobReference
|
||||
{
|
||||
Kind = "preBinary",
|
||||
Digest = "sha256:old123",
|
||||
MediaType = "application/octet-stream",
|
||||
SizeBytes = 10_000_000
|
||||
},
|
||||
new LargeBlobReference
|
||||
{
|
||||
Kind = "postBinary",
|
||||
Digest = "sha256:new456",
|
||||
MediaType = "application/octet-stream",
|
||||
SizeBytes = 10_500_000
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static DeltaSigPredicate CreatePredicateWithSbomDigest()
|
||||
{
|
||||
return new DeltaSigPredicate
|
||||
{
|
||||
Subject = new[]
|
||||
{
|
||||
new DeltaSigSubject
|
||||
{
|
||||
Uri = "oci://registry/app@sha256:test",
|
||||
Digest = new Dictionary<string, string> { ["sha256"] = "test" },
|
||||
Arch = "linux-amd64",
|
||||
Role = "old"
|
||||
},
|
||||
new DeltaSigSubject
|
||||
{
|
||||
Uri = "oci://registry/app@sha256:test2",
|
||||
Digest = new Dictionary<string, string> { ["sha256"] = "test2" },
|
||||
Arch = "linux-amd64",
|
||||
Role = "new"
|
||||
}
|
||||
},
|
||||
Delta = Array.Empty<FunctionDelta>(),
|
||||
Summary = new DeltaSummary(),
|
||||
Tooling = new DeltaTooling
|
||||
{
|
||||
Lifter = "b2r2",
|
||||
LifterVersion = "0.7.0",
|
||||
CanonicalIr = "b2r2-lowuir",
|
||||
DiffAlgorithm = "byte"
|
||||
},
|
||||
ComputedAt = DateTimeOffset.UtcNow,
|
||||
SbomDigest = "sha256:sbom1234567890abcdef"
|
||||
};
|
||||
}
|
||||
|
||||
private static DeltaSigPredicate CreateMinimalPredicate()
|
||||
{
|
||||
return new DeltaSigPredicate
|
||||
{
|
||||
Subject = new[]
|
||||
{
|
||||
new DeltaSigSubject
|
||||
{
|
||||
Uri = "oci://registry/app@sha256:min",
|
||||
Digest = new Dictionary<string, string> { ["sha256"] = "min" },
|
||||
Arch = "linux-amd64",
|
||||
Role = "old"
|
||||
},
|
||||
new DeltaSigSubject
|
||||
{
|
||||
Uri = "oci://registry/app@sha256:min2",
|
||||
Digest = new Dictionary<string, string> { ["sha256"] = "min2" },
|
||||
Arch = "linux-amd64",
|
||||
Role = "new"
|
||||
}
|
||||
},
|
||||
Delta = Array.Empty<FunctionDelta>(),
|
||||
Summary = new DeltaSummary(),
|
||||
Tooling = new DeltaTooling
|
||||
{
|
||||
Lifter = "b2r2",
|
||||
LifterVersion = "0.7.0",
|
||||
CanonicalIr = "b2r2-lowuir",
|
||||
DiffAlgorithm = "byte"
|
||||
},
|
||||
ComputedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private static DeltaSigPredicateV2 CreatePredicateV2WithLargeBlobs()
|
||||
{
|
||||
return new DeltaSigPredicateV2
|
||||
{
|
||||
Subject = new DeltaSigSubjectV2
|
||||
{
|
||||
Purl = "pkg:oci/app@sha256:test",
|
||||
Digest = new Dictionary<string, string> { ["sha256"] = "test" }
|
||||
},
|
||||
FunctionMatches = Array.Empty<FunctionMatchV2>(),
|
||||
Verdict = "patched",
|
||||
ComputedAt = DateTimeOffset.UtcNow,
|
||||
Tooling = new DeltaToolingV2
|
||||
{
|
||||
Lifter = "ghidra",
|
||||
LifterVersion = "11.0",
|
||||
CanonicalIr = "ghidra-pcode",
|
||||
MatchAlgorithm = "semantic_ksg",
|
||||
BinaryIndexVersion = "1.0.0"
|
||||
},
|
||||
Summary = new DeltaSummaryV2
|
||||
{
|
||||
TotalFunctions = 50
|
||||
},
|
||||
SbomDigest = "sha256:sbom_v2_digest",
|
||||
LargeBlobs = new[]
|
||||
{
|
||||
new LargeBlobReference
|
||||
{
|
||||
Kind = "preBinary",
|
||||
Digest = "sha256:pre_v2",
|
||||
SizeBytes = 5_000_000
|
||||
},
|
||||
new LargeBlobReference
|
||||
{
|
||||
Kind = "postBinary",
|
||||
Digest = "sha256:post_v2",
|
||||
SizeBytes = 5_100_000
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -216,15 +216,19 @@ public sealed class DeltaSigEndToEndTests
|
||||
// Assert
|
||||
deserialized.PredicateType.Should().Be(originalPredicate.PredicateType);
|
||||
deserialized.Summary.FunctionsAdded.Should().Be(originalPredicate.Summary.FunctionsAdded);
|
||||
deserialized.Subject.Should().HaveCount(originalPredicate.Subject.Count);
|
||||
deserialized.Subject.Should().HaveCount(originalPredicate.Subject.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Generate_WithSemanticSimilarity_IncludesSimilarityScores()
|
||||
{
|
||||
// Arrange
|
||||
var options = CreateOptions();
|
||||
options.Value.IncludeSemanticSimilarity = true;
|
||||
var options = Options.Create(new DeltaSigServiceOptions
|
||||
{
|
||||
PredicateType = "https://stellaops.io/delta-sig/v1",
|
||||
IncludeSemanticSimilarity = true,
|
||||
RekorUrl = "https://rekor.sigstore.dev"
|
||||
});
|
||||
var service = CreateService(options);
|
||||
|
||||
var beforeBinary = CreateTestBinaryWithModifications("libtest-1.0.so", 5, modifyIndices: new[] { 2 });
|
||||
@@ -497,3 +501,118 @@ public sealed class MockSigningService
|
||||
Signatures: ImmutableArray.Create(new DsseSignature("key-1", signature))));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DeltaSigService : IDeltaSigService
|
||||
{
|
||||
private readonly IOptions<DeltaSigServiceOptions> _options;
|
||||
private readonly MockRekorClient _rekorClient;
|
||||
private readonly MockSigningService _signingService;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
|
||||
public DeltaSigService(
|
||||
IOptions<DeltaSigServiceOptions> options,
|
||||
MockRekorClient rekorClient,
|
||||
MockSigningService signingService,
|
||||
TimeProvider timeProvider,
|
||||
Microsoft.Extensions.Logging.ILogger logger)
|
||||
{
|
||||
_options = options;
|
||||
_rekorClient = rekorClient;
|
||||
_signingService = signingService;
|
||||
_timeProvider = timeProvider;
|
||||
}
|
||||
|
||||
public Task<DeltaSigPredicate> GenerateAsync(TestBinaryData before, TestBinaryData after, CancellationToken ct)
|
||||
{
|
||||
var addedCount = Math.Max(0, after.Functions.Length - before.Functions.Length);
|
||||
var removedCount = Math.Max(0, before.Functions.Length - after.Functions.Length);
|
||||
var commonCount = Math.Min(before.Functions.Length, after.Functions.Length);
|
||||
|
||||
var diffs = new List<DeltaSigDiffEntry>();
|
||||
for (int i = 0; i < commonCount; i++)
|
||||
{
|
||||
if (before.Functions[i].Hash != after.Functions[i].Hash)
|
||||
diffs.Add(new DeltaSigDiffEntry(after.Functions[i].Name, "modified",
|
||||
before.Functions[i].Hash, after.Functions[i].Hash,
|
||||
Math.Abs(after.Functions[i].Size - before.Functions[i].Size),
|
||||
_options.Value.IncludeSemanticSimilarity ? 0.85 : null));
|
||||
}
|
||||
|
||||
var subjects = ImmutableArray.Create(
|
||||
new InTotoSubject(before.Name, ImmutableDictionary<string, string>.Empty.Add("sha256", before.Digest)),
|
||||
new InTotoSubject(after.Name, ImmutableDictionary<string, string>.Empty.Add("sha256", after.Digest)));
|
||||
|
||||
var modifiedCount = diffs.Count;
|
||||
var summary = new DeltaSigSummary(addedCount, removedCount, modifiedCount, diffs.Sum(d => d.BytesDelta));
|
||||
|
||||
return Task.FromResult(new DeltaSigPredicate(
|
||||
_options.Value.PredicateType,
|
||||
subjects,
|
||||
diffs.ToImmutableArray(),
|
||||
summary,
|
||||
_timeProvider.GetUtcNow(),
|
||||
before.Digest,
|
||||
after.Digest));
|
||||
}
|
||||
|
||||
public async Task<DsseEnvelope> SignAsync(DeltaSigPredicate predicate, CancellationToken ct)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(predicate);
|
||||
return await _signingService.SignAsync(json, ct);
|
||||
}
|
||||
|
||||
public async Task<RekorSubmissionResult> SubmitToRekorAsync(DsseEnvelope envelope, CancellationToken ct)
|
||||
{
|
||||
var payload = Encoding.UTF8.GetBytes(envelope.Payload);
|
||||
return await _rekorClient.SubmitAsync(payload, ct);
|
||||
}
|
||||
|
||||
public Task<VerificationResult> VerifyFromRekorAsync(string entryId, CancellationToken ct)
|
||||
{
|
||||
return Task.FromResult(new VerificationResult(true, _options.Value.PredicateType, null, "online"));
|
||||
}
|
||||
|
||||
public Task<VerificationResult> VerifyEnvelopeAsync(DsseEnvelope envelope, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var payloadBytes = Convert.FromBase64String(envelope.Payload);
|
||||
var payloadStr = Encoding.UTF8.GetString(payloadBytes);
|
||||
var expectedSig = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(payloadStr)));
|
||||
var isValid = envelope.Signatures.Any(s => s.Sig == expectedSig);
|
||||
return Task.FromResult(new VerificationResult(isValid, null,
|
||||
isValid ? null : "signature mismatch", null));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Task.FromResult(new VerificationResult(false, null, "signature verification failed", null));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PolicyGateResult> EvaluatePolicyAsync(DeltaSigPredicate predicate, DeltaScopePolicyOptions options, CancellationToken ct)
|
||||
{
|
||||
var violations = new List<string>();
|
||||
if (predicate.Summary.FunctionsAdded > options.MaxAddedFunctions)
|
||||
violations.Add($"Too many functions added: {predicate.Summary.FunctionsAdded} > {options.MaxAddedFunctions}");
|
||||
if (predicate.Summary.FunctionsRemoved > options.MaxRemovedFunctions)
|
||||
violations.Add($"Too many functions removed: {predicate.Summary.FunctionsRemoved} > {options.MaxRemovedFunctions}");
|
||||
|
||||
return Task.FromResult(new PolicyGateResult(violations.Count == 0, violations.ToImmutableArray()));
|
||||
}
|
||||
|
||||
public string SerializePredicate(DeltaSigPredicate predicate) => JsonSerializer.Serialize(predicate);
|
||||
|
||||
public DeltaSigPredicate DeserializePredicate(string json) => JsonSerializer.Deserialize<DeltaSigPredicate>(json)!;
|
||||
|
||||
public async Task<InclusionProof> GetInclusionProofAsync(string entryId, CancellationToken ct)
|
||||
{
|
||||
var proof = await _rekorClient.GetProofAsync(entryId, ct);
|
||||
return proof ?? new InclusionProof(0, "", ImmutableArray<string>.Empty);
|
||||
}
|
||||
|
||||
public Task<VerificationResult> VerifyWithStoredProofAsync(DsseEnvelope envelope, InclusionProof proof, CancellationToken ct)
|
||||
{
|
||||
var isValid = proof.TreeSize > 0;
|
||||
return Task.FromResult(new VerificationResult(isValid, null, null, "offline"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user