save progress
This commit is contained in:
@@ -0,0 +1,429 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Sprint: SPRINT_20260102_001_BE
|
||||
// Task: DS-041 - VEX evidence emission for backport detection
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scanner.Evidence.Models;
|
||||
|
||||
namespace StellaOps.Scanner.Evidence.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class DeltaSigVexEmitterTests
|
||||
{
|
||||
private readonly FakeTimeProvider _timeProvider = new();
|
||||
private readonly DeltaSigVexEmitter _emitter;
|
||||
|
||||
public DeltaSigVexEmitterTests()
|
||||
{
|
||||
_timeProvider.SetUtcNow(new DateTimeOffset(2026, 1, 3, 12, 0, 0, TimeSpan.Zero));
|
||||
_emitter = new DeltaSigVexEmitter(timeProvider: _timeProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_PatchedBinary_EmitsCandidate()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreatePatchedEvidence(
|
||||
cveId: "CVE-2025-12345",
|
||||
confidence: 0.95m);
|
||||
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result.CandidatesEmitted);
|
||||
Assert.Single(result.Candidates);
|
||||
|
||||
var candidate = result.Candidates[0];
|
||||
Assert.Equal(DeltaSigVexStatus.NotAffected, candidate.SuggestedStatus);
|
||||
Assert.Equal(DeltaSigVexJustification.VulnerableCodeNotPresent, candidate.Justification);
|
||||
Assert.Contains("CVE-2025-12345", candidate.CveIds);
|
||||
Assert.Equal(0.95m, candidate.Confidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_VulnerableBinary_DoesNotEmitCandidate()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreateVulnerableEvidence("CVE-2025-12345");
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result.CandidatesEmitted);
|
||||
Assert.Empty(result.Candidates);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_InconclusiveAnalysis_DoesNotEmitCandidate()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreateInconclusiveEvidence("CVE-2025-12345");
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result.CandidatesEmitted);
|
||||
Assert.Empty(result.Candidates);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_LowConfidence_DoesNotEmitCandidate()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreatePatchedEvidence(
|
||||
cveId: "CVE-2025-12345",
|
||||
confidence: 0.50m); // Below default threshold of 0.75
|
||||
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result.CandidatesEmitted);
|
||||
Assert.Empty(result.Candidates);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_CustomMinConfidence_RespectsThreshold()
|
||||
{
|
||||
// Arrange
|
||||
var options = new DeltaSigVexEmitterOptions { MinConfidence = 0.40m };
|
||||
var emitter = new DeltaSigVexEmitter(options, _timeProvider);
|
||||
|
||||
var evidence = CreatePatchedEvidence(
|
||||
cveId: "CVE-2025-12345",
|
||||
confidence: 0.50m);
|
||||
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, result.CandidatesEmitted);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_HighConfidence_DoesNotRequireReview()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreatePatchedEvidence(
|
||||
cveId: "CVE-2025-12345",
|
||||
confidence: 0.99m); // Above auto-approval threshold of 0.95
|
||||
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result.Candidates);
|
||||
Assert.False(result.Candidates[0].RequiresReview);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_MediumConfidence_RequiresReview()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreatePatchedEvidence(
|
||||
cveId: "CVE-2025-12345",
|
||||
confidence: 0.85m); // Between min and auto-approval thresholds
|
||||
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result.Candidates);
|
||||
Assert.True(result.Candidates[0].RequiresReview);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_MultiplePatchedBinaries_EmitsMultipleCandidates()
|
||||
{
|
||||
// Arrange
|
||||
var evidence1 = CreatePatchedEvidence("CVE-2025-0001", confidence: 0.90m);
|
||||
var evidence2 = CreatePatchedEvidence("CVE-2025-0002", confidence: 0.85m);
|
||||
var evidence3 = CreateVulnerableEvidence("CVE-2025-0003"); // Should be skipped
|
||||
|
||||
var context = CreateContext([evidence1, evidence2, evidence3]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result.CandidatesEmitted);
|
||||
Assert.All(result.Candidates, c => Assert.Equal(DeltaSigVexStatus.NotAffected, c.SuggestedStatus));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_RespectsMaxCandidatesLimit()
|
||||
{
|
||||
// Arrange
|
||||
var options = new DeltaSigVexEmitterOptions { MaxCandidatesPerBatch = 2 };
|
||||
var emitter = new DeltaSigVexEmitter(options, _timeProvider);
|
||||
|
||||
var evidenceList = Enumerable.Range(1, 10)
|
||||
.Select(i => CreatePatchedEvidence($"CVE-2025-{i:D4}", confidence: 0.90m))
|
||||
.ToList();
|
||||
|
||||
var context = CreateContext(evidenceList);
|
||||
|
||||
// Act
|
||||
var result = emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, result.CandidatesEmitted);
|
||||
Assert.Equal(2, result.Candidates.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_CandidateContainsSymbolMatchEvidence()
|
||||
{
|
||||
// Arrange
|
||||
var symbolMatches = ImmutableArray.Create(
|
||||
new SymbolMatchEvidence
|
||||
{
|
||||
SymbolName = "vulnerable_function",
|
||||
HashHex = "abc123def456",
|
||||
State = SignatureState.Patched,
|
||||
Confidence = 1.0m,
|
||||
ExactMatch = true
|
||||
},
|
||||
new SymbolMatchEvidence
|
||||
{
|
||||
SymbolName = "another_function",
|
||||
HashHex = "789xyz012",
|
||||
State = SignatureState.Patched,
|
||||
Confidence = 0.85m,
|
||||
ExactMatch = false,
|
||||
ChunksMatched = 17,
|
||||
ChunksTotal = 20
|
||||
});
|
||||
|
||||
var evidence = DeltaSignatureEvidence.CreatePatched(
|
||||
cveIds: ["CVE-2025-12345"],
|
||||
packagePurl: "pkg:rpm/openssl@1.0.1e-30.el6_6?arch=x86_64",
|
||||
binaryId: "build-id:abc123",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: symbolMatches,
|
||||
confidence: 0.92m,
|
||||
recipe: new NormalizationRecipeRef
|
||||
{
|
||||
RecipeId = "stellaops.normalize.x64.v1",
|
||||
Version = "1.0.0"
|
||||
},
|
||||
timeProvider: _timeProvider);
|
||||
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
Assert.Single(result.Candidates);
|
||||
var candidate = result.Candidates[0];
|
||||
|
||||
// Should have evidence links for patched symbols
|
||||
Assert.Contains(candidate.EvidenceLinks, e => e.Type == "patched_symbol");
|
||||
Assert.Equal(2, candidate.EvidenceLinks.Count(e => e.Type == "patched_symbol"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_CandidateHasCorrectExpiration()
|
||||
{
|
||||
// Arrange
|
||||
var options = new DeltaSigVexEmitterOptions { CandidateTtl = TimeSpan.FromDays(14) };
|
||||
var emitter = new DeltaSigVexEmitter(options, _timeProvider);
|
||||
|
||||
var evidence = CreatePatchedEvidence("CVE-2025-12345", confidence: 0.90m);
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
var candidate = result.Candidates[0];
|
||||
var expectedExpiry = _timeProvider.GetUtcNow().AddDays(14);
|
||||
Assert.Equal(expectedExpiry, candidate.ExpiresAt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_GeneratesUniqueCandidateIds()
|
||||
{
|
||||
// Arrange
|
||||
var evidence1 = CreatePatchedEvidence("CVE-2025-0001", confidence: 0.90m);
|
||||
var evidence2 = CreatePatchedEvidence("CVE-2025-0002", confidence: 0.85m);
|
||||
|
||||
var context = CreateContext([evidence1, evidence2]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
var ids = result.Candidates.Select(c => c.CandidateId).ToList();
|
||||
Assert.Equal(2, ids.Distinct().Count());
|
||||
Assert.All(ids, id => Assert.StartsWith("vexds-", id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_RationaleIncludesMatchDetails()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = CreatePatchedEvidence("CVE-2025-12345", confidence: 0.95m);
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
var candidate = result.Candidates[0];
|
||||
Assert.Contains("delta signature analysis", candidate.Rationale.ToLowerInvariant());
|
||||
Assert.Contains("95", candidate.Rationale); // Confidence percentage
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmitCandidates_IncludesAttestationLinkWhenPresent()
|
||||
{
|
||||
// Arrange
|
||||
var symbolMatches = ImmutableArray.Create(
|
||||
new SymbolMatchEvidence
|
||||
{
|
||||
SymbolName = "vuln_func",
|
||||
HashHex = "abc123",
|
||||
State = SignatureState.Patched,
|
||||
Confidence = 1.0m,
|
||||
ExactMatch = true
|
||||
});
|
||||
|
||||
var evidence = DeltaSignatureEvidence.CreatePatched(
|
||||
cveIds: ["CVE-2025-12345"],
|
||||
packagePurl: "pkg:rpm/test@1.0.0",
|
||||
binaryId: "build-id:xyz",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: symbolMatches,
|
||||
confidence: 0.95m,
|
||||
recipe: new NormalizationRecipeRef
|
||||
{
|
||||
RecipeId = "test.recipe",
|
||||
Version = "1.0.0"
|
||||
},
|
||||
timeProvider: _timeProvider) with
|
||||
{
|
||||
AttestationUri = "dsse://rekor.example.com/entries/abc123"
|
||||
};
|
||||
|
||||
var context = CreateContext([evidence]);
|
||||
|
||||
// Act
|
||||
var result = _emitter.EmitCandidates(context);
|
||||
|
||||
// Assert
|
||||
var candidate = result.Candidates[0];
|
||||
Assert.Contains(candidate.EvidenceLinks, e => e.Type == "dsse_attestation");
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
private DeltaSigVexEmissionContext CreateContext(IReadOnlyList<DeltaSignatureEvidence> evidence)
|
||||
{
|
||||
return new DeltaSigVexEmissionContext(
|
||||
ImageDigest: "sha256:abc123def456",
|
||||
EvidenceItems: evidence);
|
||||
}
|
||||
|
||||
private DeltaSignatureEvidence CreatePatchedEvidence(string cveId, decimal confidence)
|
||||
{
|
||||
var symbolMatches = ImmutableArray.Create(
|
||||
new SymbolMatchEvidence
|
||||
{
|
||||
SymbolName = "vulnerable_function",
|
||||
HashHex = "abc123def456",
|
||||
State = SignatureState.Patched,
|
||||
Confidence = confidence,
|
||||
ExactMatch = confidence >= 0.90m
|
||||
});
|
||||
|
||||
return DeltaSignatureEvidence.CreatePatched(
|
||||
cveIds: [cveId],
|
||||
packagePurl: "pkg:rpm/openssl@1.0.1e-30.el6_6?arch=x86_64",
|
||||
binaryId: "build-id:abc123",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: symbolMatches,
|
||||
confidence: confidence,
|
||||
recipe: new NormalizationRecipeRef
|
||||
{
|
||||
RecipeId = "stellaops.normalize.x64.v1",
|
||||
Version = "1.0.0",
|
||||
Steps = ["zero_addresses", "canonicalize_nops", "normalize_plt"]
|
||||
},
|
||||
timeProvider: _timeProvider);
|
||||
}
|
||||
|
||||
private DeltaSignatureEvidence CreateVulnerableEvidence(string cveId)
|
||||
{
|
||||
var symbolMatches = ImmutableArray.Create(
|
||||
new SymbolMatchEvidence
|
||||
{
|
||||
SymbolName = "vulnerable_function",
|
||||
HashHex = "vuln_hash_123",
|
||||
State = SignatureState.Vulnerable,
|
||||
Confidence = 0.95m,
|
||||
ExactMatch = true
|
||||
});
|
||||
|
||||
return DeltaSignatureEvidence.CreateVulnerable(
|
||||
cveIds: [cveId],
|
||||
packagePurl: "pkg:rpm/openssl@1.0.1e-30.el6_6?arch=x86_64",
|
||||
binaryId: "build-id:abc123",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: symbolMatches,
|
||||
confidence: 0.95m,
|
||||
recipe: new NormalizationRecipeRef
|
||||
{
|
||||
RecipeId = "stellaops.normalize.x64.v1",
|
||||
Version = "1.0.0"
|
||||
},
|
||||
timeProvider: _timeProvider);
|
||||
}
|
||||
|
||||
private DeltaSignatureEvidence CreateInconclusiveEvidence(string cveId)
|
||||
{
|
||||
return DeltaSignatureEvidence.CreateInconclusive(
|
||||
cveIds: [cveId],
|
||||
packagePurl: "pkg:rpm/openssl@1.0.1e-30.el6_6?arch=x86_64",
|
||||
binaryId: "build-id:abc123",
|
||||
architecture: "x86_64",
|
||||
reason: "Target symbols not found in binary",
|
||||
symbolMatches: [],
|
||||
recipe: new NormalizationRecipeRef
|
||||
{
|
||||
RecipeId = "stellaops.normalize.x64.v1",
|
||||
Version = "1.0.0"
|
||||
},
|
||||
timeProvider: _timeProvider);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fake TimeProvider for deterministic testing.
|
||||
/// </summary>
|
||||
internal sealed class FakeTimeProvider : TimeProvider
|
||||
{
|
||||
private DateTimeOffset _utcNow = DateTimeOffset.UtcNow;
|
||||
|
||||
public void SetUtcNow(DateTimeOffset value) => _utcNow = value;
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _utcNow;
|
||||
}
|
||||
@@ -0,0 +1,306 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Sprint: SPRINT_20260102_001_BE
|
||||
// Task: DS-041 - VEX evidence emission for backport detection
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using StellaOps.Scanner.Evidence.Models;
|
||||
|
||||
namespace StellaOps.Scanner.Evidence.Tests;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class DeltaSignatureEvidenceTests
|
||||
{
|
||||
private readonly FakeTimeProvider _timeProvider = new();
|
||||
|
||||
public DeltaSignatureEvidenceTests()
|
||||
{
|
||||
_timeProvider.SetUtcNow(new DateTimeOffset(2026, 1, 3, 12, 0, 0, TimeSpan.Zero));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatePatched_ReturnsCorrectResult()
|
||||
{
|
||||
// Arrange
|
||||
var symbolMatches = CreateSymbolMatches(SignatureState.Patched);
|
||||
|
||||
// Act
|
||||
var evidence = DeltaSignatureEvidence.CreatePatched(
|
||||
cveIds: ["CVE-2025-12345"],
|
||||
packagePurl: "pkg:rpm/openssl@1.0.1e",
|
||||
binaryId: "build-id:abc123",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: symbolMatches,
|
||||
confidence: 0.95m,
|
||||
recipe: CreateRecipe(),
|
||||
timeProvider: _timeProvider);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(DeltaSigResult.Patched, evidence.Result);
|
||||
Assert.Contains("PATCHED", evidence.Summary);
|
||||
Assert.Contains("95", evidence.Summary);
|
||||
Assert.Equal(0.95m, evidence.Confidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateVulnerable_ReturnsCorrectResult()
|
||||
{
|
||||
// Arrange
|
||||
var symbolMatches = CreateSymbolMatches(SignatureState.Vulnerable);
|
||||
|
||||
// Act
|
||||
var evidence = DeltaSignatureEvidence.CreateVulnerable(
|
||||
cveIds: ["CVE-2025-12345"],
|
||||
packagePurl: "pkg:rpm/openssl@1.0.1e",
|
||||
binaryId: "build-id:abc123",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: symbolMatches,
|
||||
confidence: 0.90m,
|
||||
recipe: CreateRecipe(),
|
||||
timeProvider: _timeProvider);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(DeltaSigResult.Vulnerable, evidence.Result);
|
||||
Assert.Contains("VULNERABLE", evidence.Summary);
|
||||
Assert.Contains("90", evidence.Summary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateInconclusive_ReturnsCorrectResult()
|
||||
{
|
||||
// Arrange & Act
|
||||
var evidence = DeltaSignatureEvidence.CreateInconclusive(
|
||||
cveIds: ["CVE-2025-12345"],
|
||||
packagePurl: "pkg:rpm/openssl@1.0.1e",
|
||||
binaryId: "build-id:abc123",
|
||||
architecture: "x86_64",
|
||||
reason: "Symbol not found",
|
||||
symbolMatches: [],
|
||||
recipe: CreateRecipe(),
|
||||
timeProvider: _timeProvider);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(DeltaSigResult.Inconclusive, evidence.Result);
|
||||
Assert.Contains("INCONCLUSIVE", evidence.Summary);
|
||||
Assert.Contains("Symbol not found", evidence.Summary);
|
||||
Assert.Equal(0m, evidence.Confidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evidence_SerializesToJson()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = DeltaSignatureEvidence.CreatePatched(
|
||||
cveIds: ["CVE-2025-12345", "CVE-2025-67890"],
|
||||
packagePurl: "pkg:rpm/openssl@1.0.1e",
|
||||
binaryId: "build-id:abc123",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: CreateSymbolMatches(SignatureState.Patched),
|
||||
confidence: 0.95m,
|
||||
recipe: CreateRecipe(),
|
||||
timeProvider: _timeProvider);
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(evidence);
|
||||
var deserialized = JsonSerializer.Deserialize<DeltaSignatureEvidence>(json);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(evidence.Result, deserialized.Result);
|
||||
Assert.Equal(evidence.CveIds, deserialized.CveIds);
|
||||
Assert.Equal(evidence.PackagePurl, deserialized.PackagePurl);
|
||||
Assert.Equal(evidence.Confidence, deserialized.Confidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeltaSigResult_SerializesAsString()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = DeltaSignatureEvidence.CreatePatched(
|
||||
cveIds: ["CVE-2025-12345"],
|
||||
packagePurl: "pkg:rpm/test@1.0.0",
|
||||
binaryId: "test",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: [],
|
||||
confidence: 0.95m,
|
||||
recipe: CreateRecipe(),
|
||||
timeProvider: _timeProvider);
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(evidence);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("\"patched\"", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SignatureState_SerializesAsString()
|
||||
{
|
||||
// Arrange
|
||||
var match = new SymbolMatchEvidence
|
||||
{
|
||||
SymbolName = "test_func",
|
||||
HashHex = "abc123",
|
||||
State = SignatureState.Patched,
|
||||
Confidence = 1.0m,
|
||||
ExactMatch = true
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(match);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("\"patched\"", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SymbolMatchEvidence_SerializesChunkInfo()
|
||||
{
|
||||
// Arrange
|
||||
var match = new SymbolMatchEvidence
|
||||
{
|
||||
SymbolName = "partial_match_func",
|
||||
HashHex = "xyz789",
|
||||
State = SignatureState.Patched,
|
||||
Confidence = 0.85m,
|
||||
ExactMatch = false,
|
||||
ChunksMatched = 17,
|
||||
ChunksTotal = 20
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(match);
|
||||
var deserialized = JsonSerializer.Deserialize<SymbolMatchEvidence>(json);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(17, deserialized.ChunksMatched);
|
||||
Assert.Equal(20, deserialized.ChunksTotal);
|
||||
Assert.False(deserialized.ExactMatch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizationRecipeRef_SerializesSteps()
|
||||
{
|
||||
// Arrange
|
||||
var recipe = new NormalizationRecipeRef
|
||||
{
|
||||
RecipeId = "stellaops.normalize.x64.v1",
|
||||
Version = "1.0.0",
|
||||
Steps = ["zero_addresses", "canonicalize_nops", "normalize_plt_got"]
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(recipe);
|
||||
var deserialized = JsonSerializer.Deserialize<NormalizationRecipeRef>(json);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(3, deserialized.Steps.Length);
|
||||
Assert.Contains("zero_addresses", deserialized.Steps);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evidence_WithAttestationUri_SerializesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var evidence = DeltaSignatureEvidence.CreatePatched(
|
||||
cveIds: ["CVE-2025-12345"],
|
||||
packagePurl: "pkg:rpm/test@1.0.0",
|
||||
binaryId: "test",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: [],
|
||||
confidence: 0.95m,
|
||||
recipe: CreateRecipe(),
|
||||
timeProvider: _timeProvider) with
|
||||
{
|
||||
AttestationUri = "dsse://rekor.sigstore.dev/entries/12345"
|
||||
};
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(evidence);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("attestationUri", json);
|
||||
Assert.Contains("rekor.sigstore.dev", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evidence_SchemaVersionIsSet()
|
||||
{
|
||||
// Arrange & Act
|
||||
var evidence = DeltaSignatureEvidence.CreatePatched(
|
||||
cveIds: ["CVE-2025-12345"],
|
||||
packagePurl: "pkg:rpm/test@1.0.0",
|
||||
binaryId: "test",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: [],
|
||||
confidence: 0.95m,
|
||||
recipe: CreateRecipe(),
|
||||
timeProvider: _timeProvider);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("stellaops.evidence.deltasig.v1", evidence.Schema);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Evidence_GeneratedAtIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var expectedTime = new DateTimeOffset(2026, 1, 3, 12, 0, 0, TimeSpan.Zero);
|
||||
_timeProvider.SetUtcNow(expectedTime);
|
||||
|
||||
// Act
|
||||
var evidence = DeltaSignatureEvidence.CreatePatched(
|
||||
cveIds: ["CVE-2025-12345"],
|
||||
packagePurl: "pkg:rpm/test@1.0.0",
|
||||
binaryId: "test",
|
||||
architecture: "x86_64",
|
||||
symbolMatches: [],
|
||||
confidence: 0.95m,
|
||||
recipe: CreateRecipe(),
|
||||
timeProvider: _timeProvider);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedTime, evidence.GeneratedAt);
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
private static ImmutableArray<SymbolMatchEvidence> CreateSymbolMatches(SignatureState state)
|
||||
{
|
||||
return
|
||||
[
|
||||
new SymbolMatchEvidence
|
||||
{
|
||||
SymbolName = "vulnerable_function",
|
||||
HashHex = "abc123def456",
|
||||
State = state,
|
||||
Confidence = 0.95m,
|
||||
ExactMatch = true
|
||||
},
|
||||
new SymbolMatchEvidence
|
||||
{
|
||||
SymbolName = "another_function",
|
||||
HashHex = "789xyz012",
|
||||
State = state,
|
||||
Confidence = 0.85m,
|
||||
ExactMatch = false,
|
||||
ChunksMatched = 17,
|
||||
ChunksTotal = 20
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
private static NormalizationRecipeRef CreateRecipe()
|
||||
{
|
||||
return new NormalizationRecipeRef
|
||||
{
|
||||
RecipeId = "stellaops.normalize.x64.v1",
|
||||
Version = "1.0.0",
|
||||
Steps = ["zero_addresses", "canonicalize_nops"]
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user