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

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

View File

@@ -399,7 +399,7 @@ internal static partial class CommandHandlers
format = format,
provider = providerName,
timestamp = DateTimeOffset.UtcNow.ToString("O"),
dataHash = Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(data)).ToLowerInvariant(),
dataHash = CryptoHashFactory.CreateDefault().ComputeHashHex(data, HashAlgorithms.Sha256),
signature = "STUB-SIGNATURE-BASE64",
keyId = "STUB-KEY-ID"
};

View File

@@ -5,7 +5,6 @@ using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
namespace StellaOps.Cli.Commands.PoE;
@@ -17,75 +16,56 @@ public class ExportCommand : Command
{
public ExportCommand() : base("export", "Export PoE artifacts for offline verification")
{
var findingOption = new Option<string?>(
name: "--finding",
description: "Specific finding to export (format: CVE-YYYY-NNNNN:pkg:...)")
var findingOption = new Option<string?>("--finding")
{
IsRequired = false
Description = "Specific finding to export (format: CVE-YYYY-NNNNN:pkg:...)",
Required = false
};
var scanIdOption = new Option<string>(
name: "--scan-id",
description: "Scan identifier")
var scanIdOption = new Option<string>("--scan-id")
{
IsRequired = true
Description = "Scan identifier",
Required = true
};
var outputOption = new Option<string>(
name: "--output",
description: "Output directory",
getDefaultValue: () => "./poe-export/");
var outputOption = new Option<string>("--output") { Description = "Output directory" };
outputOption.SetDefaultValue("./poe-export/");
var allReachableOption = new Option<bool>(
name: "--all-reachable",
description: "Export all reachable findings in scan",
getDefaultValue: () => false);
var allReachableOption = new Option<bool>("--all-reachable") { Description = "Export all reachable findings in scan" };
var includeRekorProofOption = new Option<bool>(
name: "--include-rekor-proof",
description: "Include Rekor inclusion proofs",
getDefaultValue: () => true);
var includeRekorProofOption = new Option<bool>("--include-rekor-proof") { Description = "Include Rekor inclusion proofs" };
includeRekorProofOption.SetDefaultValue(true);
var includeSubgraphOption = new Option<bool>(
name: "--include-subgraph",
description: "Include parent richgraph-v1",
getDefaultValue: () => false);
var includeSubgraphOption = new Option<bool>("--include-subgraph") { Description = "Include parent richgraph-v1" };
var includeSbomOption = new Option<bool>(
name: "--include-sbom",
description: "Include SBOM artifact",
getDefaultValue: () => false);
var includeSbomOption = new Option<bool>("--include-sbom") { Description = "Include SBOM artifact" };
var formatOption = new Option<ArchiveFormat>(
name: "--format",
description: "Archive format",
getDefaultValue: () => ArchiveFormat.TarGz);
var formatOption = new Option<ArchiveFormat>("--format") { Description = "Archive format" };
formatOption.SetDefaultValue(ArchiveFormat.TarGz);
var casRootOption = new Option<string?>(
name: "--cas-root",
description: "CAS root directory (default: from config)");
var casRootOption = new Option<string?>("--cas-root") { Description = "CAS root directory (default: from config)" };
AddOption(findingOption);
AddOption(scanIdOption);
AddOption(outputOption);
AddOption(allReachableOption);
AddOption(includeRekorProofOption);
AddOption(includeSubgraphOption);
AddOption(includeSbomOption);
AddOption(formatOption);
AddOption(casRootOption);
Add(findingOption);
Add(scanIdOption);
Add(outputOption);
Add(allReachableOption);
Add(includeRekorProofOption);
Add(includeSubgraphOption);
Add(includeSbomOption);
Add(formatOption);
Add(casRootOption);
this.SetHandler(async (context) =>
SetAction(async (parseResult, _) =>
{
var finding = context.ParseResult.GetValueForOption(findingOption);
var scanId = context.ParseResult.GetValueForOption(scanIdOption)!;
var output = context.ParseResult.GetValueForOption(outputOption)!;
var allReachable = context.ParseResult.GetValueForOption(allReachableOption);
var includeRekor = context.ParseResult.GetValueForOption(includeRekorProofOption);
var includeSubgraph = context.ParseResult.GetValueForOption(includeSubgraphOption);
var includeSbom = context.ParseResult.GetValueForOption(includeSbomOption);
var format = context.ParseResult.GetValueForOption(formatOption);
var casRoot = context.ParseResult.GetValueForOption(casRootOption);
var finding = parseResult.GetValue(findingOption);
var scanId = parseResult.GetValue(scanIdOption) ?? string.Empty;
var output = parseResult.GetValue(outputOption) ?? "./poe-export/";
var allReachable = parseResult.GetValue(allReachableOption);
var includeRekor = parseResult.GetValue(includeRekorProofOption);
var includeSubgraph = parseResult.GetValue(includeSubgraphOption);
var includeSbom = parseResult.GetValue(includeSbomOption);
var format = parseResult.GetValue(formatOption);
var casRoot = parseResult.GetValue(casRootOption);
var exporter = new PoEExporter(Console.WriteLine);
await exporter.ExportAsync(new ExportOptions(
@@ -97,10 +77,9 @@ public class ExportCommand : Command
IncludeSubgraph: includeSubgraph,
IncludeSbom: includeSbom,
Format: format,
CasRoot: casRoot
));
CasRoot: casRoot));
context.ExitCode = 0;
return 0;
});
}
}

View File

@@ -4,7 +4,6 @@ using System.CommandLine;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
namespace StellaOps.Cli.Commands.PoE;
@@ -16,63 +15,52 @@ public class VerifyCommand : Command
{
public VerifyCommand() : base("verify", "Verify a Proof of Exposure artifact")
{
var poeOption = new Option<string>(
name: "--poe",
description: "PoE hash (blake3:...) or file path to poe.json")
var poeOption = new Option<string>("--poe")
{
IsRequired = true
Description = "PoE hash (blake3:...) or file path to poe.json",
Required = true
};
var offlineOption = new Option<bool>(
name: "--offline",
description: "Enable offline mode (no network access)",
getDefaultValue: () => false);
var offlineOption = new Option<bool>("--offline") { Description = "Enable offline mode (no network access)" };
var trustedKeysOption = new Option<string?>(
name: "--trusted-keys",
description: "Path to trusted-keys.json file");
var trustedKeysOption = new Option<string?>("--trusted-keys") { Description = "Path to trusted-keys.json file" };
var checkPolicyOption = new Option<string?>(
name: "--check-policy",
description: "Verify policy digest matches expected value (sha256:...)");
var rekorCheckpointOption = new Option<string?>(
name: "--rekor-checkpoint",
description: "Path to cached Rekor checkpoint file");
var verboseOption = new Option<bool>(
name: "--verbose",
description: "Detailed verification output",
getDefaultValue: () => false);
var outputFormatOption = new Option<OutputFormat>(
name: "--output",
description: "Output format",
getDefaultValue: () => OutputFormat.Table);
var casRootOption = new Option<string?>(
name: "--cas-root",
description: "Local CAS root directory for offline mode");
AddOption(poeOption);
AddOption(offlineOption);
AddOption(trustedKeysOption);
AddOption(checkPolicyOption);
AddOption(rekorCheckpointOption);
AddOption(verboseOption);
AddOption(outputFormatOption);
AddOption(casRootOption);
this.SetHandler(async (context) =>
var checkPolicyOption = new Option<string?>("--check-policy")
{
var poe = context.ParseResult.GetValueForOption(poeOption)!;
var offline = context.ParseResult.GetValueForOption(offlineOption);
var trustedKeys = context.ParseResult.GetValueForOption(trustedKeysOption);
var checkPolicy = context.ParseResult.GetValueForOption(checkPolicyOption);
var rekorCheckpoint = context.ParseResult.GetValueForOption(rekorCheckpointOption);
var verbose = context.ParseResult.GetValueForOption(verboseOption);
var outputFormat = context.ParseResult.GetValueForOption(outputFormatOption);
var casRoot = context.ParseResult.GetValueForOption(casRootOption);
Description = "Verify policy digest matches expected value (sha256:...)"
};
var rekorCheckpointOption = new Option<string?>("--rekor-checkpoint")
{
Description = "Path to cached Rekor checkpoint file"
};
var verboseOption = new Option<bool>("--verbose") { Description = "Detailed verification output" };
var outputFormatOption = new Option<OutputFormat>("--output") { Description = "Output format" };
outputFormatOption.SetDefaultValue(OutputFormat.Table);
var casRootOption = new Option<string?>("--cas-root") { Description = "Local CAS root directory for offline mode" };
Add(poeOption);
Add(offlineOption);
Add(trustedKeysOption);
Add(checkPolicyOption);
Add(rekorCheckpointOption);
Add(verboseOption);
Add(outputFormatOption);
Add(casRootOption);
SetAction(async (parseResult, _) =>
{
var poe = parseResult.GetValue(poeOption) ?? string.Empty;
var offline = parseResult.GetValue(offlineOption);
var trustedKeys = parseResult.GetValue(trustedKeysOption);
var checkPolicy = parseResult.GetValue(checkPolicyOption);
var rekorCheckpoint = parseResult.GetValue(rekorCheckpointOption);
var verbose = parseResult.GetValue(verboseOption);
var outputFormat = parseResult.GetValue(outputFormatOption);
var casRoot = parseResult.GetValue(casRootOption);
var verifier = new PoEVerifier(Console.WriteLine, verbose);
var result = await verifier.VerifyAsync(new VerifyOptions(
@@ -83,10 +71,9 @@ public class VerifyCommand : Command
RekorCheckpointPath: rekorCheckpoint,
Verbose: verbose,
OutputFormat: outputFormat,
CasRoot: casRoot
));
CasRoot: casRoot));
context.ExitCode = result.IsVerified ? 0 : 1;
return result.IsVerified ? 0 : 1;
});
}
}
@@ -210,7 +197,7 @@ public class PoEVerifier
var policyDigest = poe.Metadata?.Policy?.PolicyDigest;
result.PolicyBindingValid = (policyDigest == options.CheckPolicyDigest);
if (result.PolicyBindingValid)
if (result.PolicyBindingValid == true)
{
_output($" ✓ Policy digest matches: {options.CheckPolicyDigest}");
}