tests fixes and sprints work

This commit is contained in:
master
2026-01-22 19:08:46 +02:00
parent c32fff8f86
commit 726d70dc7f
881 changed files with 134434 additions and 6228 deletions

File diff suppressed because it is too large Load Diff

168
src/Verifier/Program.cs Normal file
View File

@@ -0,0 +1,168 @@
// -----------------------------------------------------------------------------
// Program.cs
// Sprint: SPRINT_20260121_036_BinaryIndex_golden_corpus_bundle_verification
// Task: GCB-003 - Implement standalone offline verifier
// Description: Entry point for standalone bundle verifier CLI
// -----------------------------------------------------------------------------
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Parsing;
using StellaOps.Verifier;
// Exit codes:
// 0: All verifications passed
// 1: One or more verifications failed
// 2: Invalid input or configuration error
var bundleOption = new Option<FileInfo>("--bundle", ["-b"])
{
Description = "Path to the evidence bundle to verify",
IsRequired = true
};
var trustedKeysOption = new Option<FileInfo?>("--trusted-keys", ["-k"])
{
Description = "Path to trusted public keys file"
};
var trustProfileOption = new Option<FileInfo?>("--trust-profile", ["-p"])
{
Description = "Path to trust profile JSON file"
};
var outputOption = new Option<FileInfo?>("--output", ["-o"])
{
Description = "Path to write verification report"
};
var formatOption = new Option<ReportFormat>("--format", ["-f"])
{
Description = "Report output format"
};
formatOption.SetDefaultValue(ReportFormat.Markdown);
var verifySignaturesOption = new Option<bool>("--verify-signatures")
{
Description = "Verify bundle manifest signatures"
};
verifySignaturesOption.SetDefaultValue(true);
var verifyTimestampsOption = new Option<bool>("--verify-timestamps")
{
Description = "Verify RFC 3161 timestamps"
};
verifyTimestampsOption.SetDefaultValue(true);
var verifyDigestsOption = new Option<bool>("--verify-digests")
{
Description = "Verify blob digests"
};
verifyDigestsOption.SetDefaultValue(true);
var verifyPairsOption = new Option<bool>("--verify-pairs")
{
Description = "Verify pair artifacts (SBOM, delta-sig)"
};
verifyPairsOption.SetDefaultValue(true);
var quietOption = new Option<bool>("--quiet", ["-q"])
{
Description = "Suppress output except for errors"
};
var verboseOption = new Option<bool>("--verbose", ["-v"])
{
Description = "Show detailed verification output"
};
var verifyCommand = new Command("verify", "Verify an evidence bundle")
{
bundleOption,
trustedKeysOption,
trustProfileOption,
outputOption,
formatOption,
verifySignaturesOption,
verifyTimestampsOption,
verifyDigestsOption,
verifyPairsOption,
quietOption,
verboseOption
};
verifyCommand.SetHandler(async (context) =>
{
var bundle = context.ParseResult.GetValueForOption(bundleOption)!;
var trustedKeys = context.ParseResult.GetValueForOption(trustedKeysOption);
var trustProfile = context.ParseResult.GetValueForOption(trustProfileOption);
var output = context.ParseResult.GetValueForOption(outputOption);
var format = context.ParseResult.GetValueForOption(formatOption);
var verifySignatures = context.ParseResult.GetValueForOption(verifySignaturesOption);
var verifyTimestamps = context.ParseResult.GetValueForOption(verifyTimestampsOption);
var verifyDigests = context.ParseResult.GetValueForOption(verifyDigestsOption);
var verifyPairs = context.ParseResult.GetValueForOption(verifyPairsOption);
var quiet = context.ParseResult.GetValueForOption(quietOption);
var verbose = context.ParseResult.GetValueForOption(verboseOption);
var options = new VerifierOptions
{
BundlePath = bundle.FullName,
TrustedKeysPath = trustedKeys?.FullName,
TrustProfilePath = trustProfile?.FullName,
OutputPath = output?.FullName,
OutputFormat = format,
VerifySignatures = verifySignatures,
VerifyTimestamps = verifyTimestamps,
VerifyDigests = verifyDigests,
VerifyPairs = verifyPairs,
Quiet = quiet,
Verbose = verbose
};
var verifier = new BundleVerifier();
var exitCode = await verifier.VerifyAsync(options, context.GetCancellationToken());
context.ExitCode = exitCode;
});
var infoCommand = new Command("info", "Display bundle information without verification")
{
bundleOption,
formatOption,
quietOption
};
infoCommand.SetHandler(async (context) =>
{
var bundle = context.ParseResult.GetValueForOption(bundleOption)!;
var format = context.ParseResult.GetValueForOption(formatOption);
var quiet = context.ParseResult.GetValueForOption(quietOption);
var verifier = new BundleVerifier();
var exitCode = await verifier.ShowInfoAsync(
bundle.FullName,
format,
quiet,
context.GetCancellationToken());
context.ExitCode = exitCode;
});
var rootCommand = new RootCommand("Stella Ops Bundle Verifier - Offline evidence bundle verification")
{
verifyCommand,
infoCommand
};
// Add version option
rootCommand.AddOption(new Option<bool>("--version", "Show version information"));
var parser = new CommandLineBuilder(rootCommand)
.UseDefaults()
.UseExceptionHandler((ex, context) =>
{
Console.Error.WriteLine($"Error: {ex.Message}");
context.ExitCode = 2;
})
.Build();
return await parser.InvokeAsync(args);

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
StellaOps.Verifier.csproj
Sprint: SPRINT_20260121_036_BinaryIndex_golden_corpus_bundle_verification
Task: GCB-003 - Implement standalone offline verifier
Description: Standalone verifier for evidence bundles in air-gapped environments
-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- Assembly metadata -->
<AssemblyName>stella-verifier</AssemblyName>
<RootNamespace>StellaOps.Verifier</RootNamespace>
<Product>Stella Ops Bundle Verifier</Product>
<Description>Standalone verifier for Stella Ops evidence bundles</Description>
<!-- Single-file and self-contained publishing -->
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<!-- Optimize for size -->
<InvariantGlobalization>true</InvariantGlobalization>
<DebuggerSupport>false</DebuggerSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
</PropertyGroup>
<!-- Runtime identifiers for cross-platform builds -->
<PropertyGroup Condition="'$(RuntimeIdentifier)' == ''">
<RuntimeIdentifiers>win-x64;linux-x64;linux-musl-x64;osx-x64;osx-arm64</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.CommandLine" />
</ItemGroup>
<!-- Minimal dependencies for standalone operation -->
<ItemGroup>
<!-- No database, network, or heavy framework dependencies -->
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Verifier", "StellaOps.Verifier.csproj", "{5E7B8A1C-0F3D-4E6B-9C2A-1D8F7E6B5A4C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.Verifier.Tests", "__Tests\StellaOps.Verifier.Tests\StellaOps.Verifier.Tests.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5E7B8A1C-0F3D-4E6B-9C2A-1D8F7E6B5A4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5E7B8A1C-0F3D-4E6B-9C2A-1D8F7E6B5A4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E7B8A1C-0F3D-4E6B-9C2A-1D8F7E6B5A4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5E7B8A1C-0F3D-4E6B-9C2A-1D8F7E6B5A4C}.Release|Any CPU.Build.0 = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,90 @@
// -----------------------------------------------------------------------------
// VerifierOptions.cs
// Sprint: SPRINT_20260121_036_BinaryIndex_golden_corpus_bundle_verification
// Task: GCB-003 - Implement standalone offline verifier
// Description: Options for bundle verification
// -----------------------------------------------------------------------------
namespace StellaOps.Verifier;
/// <summary>
/// Options for bundle verification.
/// </summary>
public sealed class VerifierOptions
{
/// <summary>
/// Path to the bundle file to verify.
/// </summary>
public required string BundlePath { get; init; }
/// <summary>
/// Path to trusted public keys file.
/// </summary>
public string? TrustedKeysPath { get; init; }
/// <summary>
/// Path to trust profile JSON file.
/// </summary>
public string? TrustProfilePath { get; init; }
/// <summary>
/// Path to write verification report.
/// </summary>
public string? OutputPath { get; init; }
/// <summary>
/// Output format for the report.
/// </summary>
public ReportFormat OutputFormat { get; init; } = ReportFormat.Markdown;
/// <summary>
/// Whether to verify signatures.
/// </summary>
public bool VerifySignatures { get; init; } = true;
/// <summary>
/// Whether to verify timestamps.
/// </summary>
public bool VerifyTimestamps { get; init; } = true;
/// <summary>
/// Whether to verify digests.
/// </summary>
public bool VerifyDigests { get; init; } = true;
/// <summary>
/// Whether to verify pair artifacts.
/// </summary>
public bool VerifyPairs { get; init; } = true;
/// <summary>
/// Suppress output except for errors.
/// </summary>
public bool Quiet { get; init; }
/// <summary>
/// Show detailed verification output.
/// </summary>
public bool Verbose { get; init; }
}
/// <summary>
/// Report output format.
/// </summary>
public enum ReportFormat
{
/// <summary>
/// Markdown format.
/// </summary>
Markdown,
/// <summary>
/// JSON format.
/// </summary>
Json,
/// <summary>
/// Plain text format (for terminal output).
/// </summary>
Text
}

View File

@@ -0,0 +1,455 @@
// -----------------------------------------------------------------------------
// BundleVerifierTests.cs
// Sprint: SPRINT_20260121_036_BinaryIndex_golden_corpus_bundle_verification
// Task: GCB-003 - Implement standalone offline verifier
// Description: Unit tests for BundleVerifier standalone verification logic
// -----------------------------------------------------------------------------
using System.IO.Compression;
using System.Text.Json;
using FluentAssertions;
using Xunit;
namespace StellaOps.Verifier.Tests;
public sealed class BundleVerifierTests : IDisposable
{
private readonly string _tempDir;
private readonly BundleVerifier _sut;
public BundleVerifierTests()
{
_tempDir = Path.Combine(Path.GetTempPath(), $"verifier-test-{Guid.NewGuid():N}");
Directory.CreateDirectory(_tempDir);
_sut = new BundleVerifier();
}
public void Dispose()
{
if (Directory.Exists(_tempDir))
{
Directory.Delete(_tempDir, recursive: true);
}
}
#region Verify Tests
[Fact]
public async Task VerifyAsync_NonexistentBundle_ReturnsError()
{
// Arrange
var options = new VerifierOptions
{
BundlePath = Path.Combine(_tempDir, "nonexistent.tar.gz"),
Quiet = true
};
// Act
var exitCode = await _sut.VerifyAsync(options);
// Assert
exitCode.Should().Be(2); // Error
}
[Fact]
public async Task VerifyAsync_ValidBundle_ReturnsPassed()
{
// Arrange
var bundlePath = CreateTestBundle("openssl", "CVE-2024-1234", "debian");
var options = new VerifierOptions
{
BundlePath = bundlePath,
VerifySignatures = false,
VerifyTimestamps = false,
VerifyDigests = true,
VerifyPairs = true,
Quiet = true
};
// Act
var exitCode = await _sut.VerifyAsync(options);
// Assert
exitCode.Should().Be(0); // Passed
}
[Fact]
public async Task VerifyAsync_BundleWithBadDigest_ReturnsFailed()
{
// Arrange
var bundlePath = CreateTestBundleWithBadDigest();
var options = new VerifierOptions
{
BundlePath = bundlePath,
VerifySignatures = false,
VerifyTimestamps = false,
VerifyDigests = true,
VerifyPairs = false,
Quiet = true
};
// Act
var exitCode = await _sut.VerifyAsync(options);
// Assert
exitCode.Should().Be(1); // Failed
}
[Fact]
public async Task VerifyAsync_UnsignedBundle_ReturnsWarning()
{
// Arrange
var bundlePath = CreateTestBundle("openssl", "CVE-2024-1234", "debian");
var options = new VerifierOptions
{
BundlePath = bundlePath,
VerifySignatures = true,
VerifyTimestamps = false,
VerifyDigests = false,
VerifyPairs = false,
Quiet = true
};
// Act
var exitCode = await _sut.VerifyAsync(options);
// Assert
exitCode.Should().Be(1); // Warning treated as failure
}
[Fact]
public async Task VerifyAsync_WithOutputReport_WritesReport()
{
// Arrange
var bundlePath = CreateTestBundle("openssl", "CVE-2024-1234", "debian");
var reportPath = Path.Combine(_tempDir, "report.md");
var options = new VerifierOptions
{
BundlePath = bundlePath,
VerifySignatures = false,
VerifyTimestamps = false,
VerifyDigests = true,
VerifyPairs = true,
OutputPath = reportPath,
OutputFormat = ReportFormat.Markdown,
Quiet = true
};
// Act
var exitCode = await _sut.VerifyAsync(options);
// Assert
exitCode.Should().Be(0);
File.Exists(reportPath).Should().BeTrue();
var content = await File.ReadAllTextAsync(reportPath);
content.Should().Contain("Bundle Verification Report");
}
[Fact]
public async Task VerifyAsync_WithJsonReport_WritesValidJson()
{
// Arrange
var bundlePath = CreateTestBundle("openssl", "CVE-2024-1234", "debian");
var reportPath = Path.Combine(_tempDir, "report.json");
var options = new VerifierOptions
{
BundlePath = bundlePath,
VerifySignatures = false,
VerifyTimestamps = false,
VerifyDigests = true,
VerifyPairs = true,
OutputPath = reportPath,
OutputFormat = ReportFormat.Json,
Quiet = true
};
// Act
var exitCode = await _sut.VerifyAsync(options);
// Assert
exitCode.Should().Be(0);
File.Exists(reportPath).Should().BeTrue();
var content = await File.ReadAllTextAsync(reportPath);
var json = JsonDocument.Parse(content);
json.RootElement.GetProperty("overallStatus").GetString().Should().Be("Passed");
}
[Fact]
public async Task VerifyAsync_WithTrustedKeys_ValidatesSignerKey()
{
// Arrange
var bundlePath = CreateTestBundleWithSignature("trusted-key-id");
var trustedKeysPath = CreateTrustedKeysFile(["trusted-key-id"]);
var options = new VerifierOptions
{
BundlePath = bundlePath,
TrustedKeysPath = trustedKeysPath,
VerifySignatures = true,
VerifyTimestamps = false,
VerifyDigests = false,
VerifyPairs = false,
Quiet = true
};
// Act
var exitCode = await _sut.VerifyAsync(options);
// Assert
exitCode.Should().Be(0); // Key is trusted
}
[Fact]
public async Task VerifyAsync_WithUntrustedKey_ReturnsFailed()
{
// Arrange
var bundlePath = CreateTestBundleWithSignature("untrusted-key-id");
var trustedKeysPath = CreateTrustedKeysFile(["trusted-key-id"]);
var options = new VerifierOptions
{
BundlePath = bundlePath,
TrustedKeysPath = trustedKeysPath,
VerifySignatures = true,
VerifyTimestamps = false,
VerifyDigests = false,
VerifyPairs = false,
Quiet = true
};
// Act
var exitCode = await _sut.VerifyAsync(options);
// Assert
exitCode.Should().Be(1); // Key not trusted
}
[Fact]
public async Task VerifyAsync_WithCancellation_ReturnsError()
{
// Arrange
var bundlePath = CreateTestBundle("openssl", "CVE-2024-1234", "debian");
var options = new VerifierOptions
{
BundlePath = bundlePath,
Quiet = true
};
using var cts = new CancellationTokenSource();
await cts.CancelAsync();
// Act
var exitCode = await _sut.VerifyAsync(options, cts.Token);
// Assert
exitCode.Should().Be(2); // Error (cancelled)
}
#endregion
#region ShowInfo Tests
[Fact]
public async Task ShowInfoAsync_ValidBundle_ReturnsZero()
{
// Arrange
var bundlePath = CreateTestBundle("openssl", "CVE-2024-1234", "debian");
// Act
var exitCode = await _sut.ShowInfoAsync(bundlePath, ReportFormat.Text, quiet: true);
// Assert
exitCode.Should().Be(0);
}
[Fact]
public async Task ShowInfoAsync_NonexistentBundle_ReturnsError()
{
// Arrange
var bundlePath = Path.Combine(_tempDir, "nonexistent.tar.gz");
// Act
var exitCode = await _sut.ShowInfoAsync(bundlePath, ReportFormat.Text, quiet: true);
// Assert
exitCode.Should().Be(2);
}
#endregion
#region Helper Methods
private string CreateTestBundle(string package, string advisoryId, string distribution)
{
var stagingDir = Path.Combine(_tempDir, $"staging-{Guid.NewGuid():N}");
Directory.CreateDirectory(stagingDir);
var pairId = $"{package}-{advisoryId}-{distribution}";
var pairDir = Path.Combine(stagingDir, "pairs", pairId);
Directory.CreateDirectory(pairDir);
// Create binaries
File.WriteAllBytes(Path.Combine(pairDir, "pre.bin"), [1, 2, 3, 4]);
File.WriteAllBytes(Path.Combine(pairDir, "post.bin"), [5, 6, 7, 8]);
// Create SBOM
var sbom = new { spdxVersion = "SPDX-3.0.1", name = $"{package}-sbom" };
var sbomContent = JsonSerializer.SerializeToUtf8Bytes(sbom);
File.WriteAllBytes(Path.Combine(pairDir, "sbom.spdx.json"), sbomContent);
var sbomDigest = ComputeHash(sbomContent);
// Create delta-sig
var predicate = new { payloadType = "application/vnd.stella-ops.delta-sig+json", payload = "test" };
var predicateContent = JsonSerializer.SerializeToUtf8Bytes(predicate);
File.WriteAllBytes(Path.Combine(pairDir, "delta-sig.dsse.json"), predicateContent);
var predicateDigest = ComputeHash(predicateContent);
// Create manifest
var manifest = new
{
bundleId = $"test-bundle-{Guid.NewGuid():N}",
schemaVersion = "1.0.0",
createdAt = DateTimeOffset.UtcNow,
generator = "BundleVerifierTests",
pairs = new[]
{
new
{
pairId,
package,
advisoryId,
distribution,
sbomDigest,
deltaSigDigest = predicateDigest
}
}
};
File.WriteAllText(
Path.Combine(stagingDir, "manifest.json"),
JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true }));
return CreateTarball(stagingDir);
}
private string CreateTestBundleWithBadDigest()
{
var stagingDir = Path.Combine(_tempDir, $"staging-{Guid.NewGuid():N}");
Directory.CreateDirectory(stagingDir);
var pairId = "openssl-CVE-2024-1234-debian";
var pairDir = Path.Combine(stagingDir, "pairs", pairId);
Directory.CreateDirectory(pairDir);
// Create SBOM with wrong digest in manifest
var sbom = new { spdxVersion = "SPDX-3.0.1", name = "openssl-sbom" };
File.WriteAllText(
Path.Combine(pairDir, "sbom.spdx.json"),
JsonSerializer.Serialize(sbom));
var manifest = new
{
bundleId = $"test-bundle-{Guid.NewGuid():N}",
schemaVersion = "1.0.0",
createdAt = DateTimeOffset.UtcNow,
generator = "Test",
pairs = new[]
{
new
{
pairId,
package = "openssl",
advisoryId = "CVE-2024-1234",
distribution = "debian",
sbomDigest = "sha256:0000000000000000000000000000000000000000000000000000000000000000", // Wrong
deltaSigDigest = (string?)null
}
}
};
File.WriteAllText(
Path.Combine(stagingDir, "manifest.json"),
JsonSerializer.Serialize(manifest));
return CreateTarball(stagingDir);
}
private string CreateTestBundleWithSignature(string keyId)
{
var stagingDir = Path.Combine(_tempDir, $"staging-{Guid.NewGuid():N}");
Directory.CreateDirectory(stagingDir);
var manifest = new
{
bundleId = $"test-bundle-{Guid.NewGuid():N}",
schemaVersion = "1.0.0",
createdAt = DateTimeOffset.UtcNow,
generator = "Test",
pairs = Array.Empty<object>()
};
File.WriteAllText(
Path.Combine(stagingDir, "manifest.json"),
JsonSerializer.Serialize(manifest));
var signature = new
{
signatureType = "cosign",
keyId,
placeholder = false
};
File.WriteAllText(
Path.Combine(stagingDir, "manifest.json.sig"),
JsonSerializer.Serialize(signature));
return CreateTarball(stagingDir);
}
private string CreateTrustedKeysFile(string[] keyIds)
{
var path = Path.Combine(_tempDir, $"trusted-keys-{Guid.NewGuid():N}.json");
var keys = new { keyIds };
File.WriteAllText(path, JsonSerializer.Serialize(keys));
return path;
}
private string CreateTarball(string sourceDir)
{
var tarPath = Path.Combine(_tempDir, $"{Guid.NewGuid():N}.tar.gz");
var tempTar = Path.GetTempFileName();
try
{
using (var tarStream = File.Create(tempTar))
{
System.Formats.Tar.TarFile.CreateFromDirectory(
sourceDir,
tarStream,
includeBaseDirectory: false);
}
using var inputStream = File.OpenRead(tempTar);
using var outputStream = File.Create(tarPath);
using var gzipStream = new GZipStream(outputStream, CompressionLevel.Optimal);
inputStream.CopyTo(gzipStream);
}
finally
{
if (File.Exists(tempTar))
{
File.Delete(tempTar);
}
Directory.Delete(sourceDir, recursive: true);
}
return tarPath;
}
private static string ComputeHash(byte[] data)
{
var hash = System.Security.Cryptography.SHA256.HashData(data);
return $"sha256:{Convert.ToHexString(hash).ToLowerInvariant()}";
}
#endregion
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
StellaOps.Verifier.Tests.csproj
Sprint: SPRINT_20260121_036_BinaryIndex_golden_corpus_bundle_verification
Task: GCB-003 - Implement standalone offline verifier
Description: Unit tests for standalone bundle verifier
-->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.Verifier.csproj" />
</ItemGroup>
</Project>