consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
138
src/Attestor/StellaOps.Provenance.Attestation.Tool/Program.cs
Normal file
138
src/Attestor/StellaOps.Provenance.Attestation.Tool/Program.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
|
||||
using StellaOps.Provenance.Attestation;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
||||
return await ToolEntrypoint.RunAsync(args, Console.Out, Console.Error, TimeProvider.System);
|
||||
|
||||
internal static class ToolEntrypoint
|
||||
{
|
||||
private const int ExitInvalid = 1;
|
||||
private const int ExitUnverified = 2;
|
||||
|
||||
public static async Task<int> RunAsync(string[] args, TextWriter stdout, TextWriter stderr, TimeProvider timeProvider)
|
||||
{
|
||||
var options = Parse(args);
|
||||
if (!options.Valid)
|
||||
{
|
||||
return Usage(stderr);
|
||||
}
|
||||
|
||||
byte[] payload;
|
||||
try
|
||||
{
|
||||
payload = options.PayloadPath == "-"
|
||||
? await ReadAllAsync(Console.OpenStandardInput())
|
||||
: await File.ReadAllBytesAsync(options.PayloadPath!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await stderr.WriteLineAsync($"read error: {ex.Message}");
|
||||
return ExitInvalid;
|
||||
}
|
||||
|
||||
byte[] signature;
|
||||
byte[] key;
|
||||
try
|
||||
{
|
||||
signature = Hex.FromHex(options.SignatureHex!);
|
||||
key = Hex.FromHex(options.KeyHex!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await stderr.WriteLineAsync($"hex parse error: {ex.Message}");
|
||||
return ExitInvalid;
|
||||
}
|
||||
|
||||
var signRequest = new SignRequest(payload, options.ContentType!, RequiredClaims: new[] { "predicateType" });
|
||||
var signResult = new SignResult(signature, options.KeyId!, options.SignedAt ?? DateTimeOffset.MinValue, null);
|
||||
|
||||
var verifier = new HmacVerifier(new InMemoryKeyProvider(options.KeyId!, key, options.NotAfter), timeProvider, options.MaxSkew);
|
||||
var verifyResult = await verifier.VerifyAsync(signRequest, signResult);
|
||||
|
||||
var json = JsonSerializer.Serialize(new
|
||||
{
|
||||
valid = verifyResult.IsValid,
|
||||
reason = verifyResult.Reason,
|
||||
verifiedAt = verifyResult.VerifiedAt.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture),
|
||||
keyId = options.KeyId,
|
||||
contentType = options.ContentType
|
||||
}, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = false });
|
||||
|
||||
await stdout.WriteLineAsync(json);
|
||||
return verifyResult.IsValid ? 0 : ExitUnverified;
|
||||
}
|
||||
|
||||
private static async Task<byte[]> ReadAllAsync(Stream stream)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await stream.CopyToAsync(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
private static int Usage(TextWriter stderr)
|
||||
{
|
||||
stderr.WriteLine("Usage: stella-forensic-verify --payload <file|-> --signature-hex <hex> --key-hex <hex> [--key-id <id>] [--content-type <ct>] [--signed-at <ISO>] [--not-after <ISO>] [--max-skew-minutes <int>]");
|
||||
stderr.WriteLine("Exit codes: 0 valid, 2 invalid signature/time, 1 bad args");
|
||||
return ExitInvalid;
|
||||
}
|
||||
|
||||
private static ParsedOptions Parse(string[] args)
|
||||
{
|
||||
string? GetArg(string name)
|
||||
{
|
||||
for (int i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
if (args[i].Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
return args[i + 1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
var payload = GetArg("--payload");
|
||||
var sig = GetArg("--signature-hex");
|
||||
var key = GetArg("--key-hex");
|
||||
if (payload is null || sig is null || key is null)
|
||||
{
|
||||
return ParsedOptions.Invalid;
|
||||
}
|
||||
|
||||
DateTimeOffset? ParseDate(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return null;
|
||||
return DateTimeOffset.Parse(value!, null, System.Globalization.DateTimeStyles.RoundtripKind);
|
||||
}
|
||||
|
||||
TimeSpan ParseSkew(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value)) return TimeSpan.FromMinutes(5);
|
||||
return TimeSpan.FromMinutes(double.Parse(value!, System.Globalization.CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return new ParsedOptions(
|
||||
Valid: true,
|
||||
PayloadPath: payload,
|
||||
SignatureHex: sig,
|
||||
KeyHex: key,
|
||||
KeyId: GetArg("--key-id") ?? "hmac",
|
||||
ContentType: GetArg("--content-type") ?? "application/octet-stream",
|
||||
SignedAt: ParseDate(GetArg("--signed-at")),
|
||||
NotAfter: ParseDate(GetArg("--not-after")),
|
||||
MaxSkew: ParseSkew(GetArg("--max-skew-minutes"))
|
||||
);
|
||||
}
|
||||
|
||||
private sealed record ParsedOptions(
|
||||
bool Valid,
|
||||
string? PayloadPath = null,
|
||||
string? SignatureHex = null,
|
||||
string? KeyHex = null,
|
||||
string? KeyId = null,
|
||||
string? ContentType = null,
|
||||
DateTimeOffset? SignedAt = null,
|
||||
DateTimeOffset? NotAfter = null,
|
||||
TimeSpan MaxSkew = default)
|
||||
{
|
||||
public static readonly ParsedOptions Invalid = new(false);
|
||||
}
|
||||
}
|
||||
34
src/Attestor/StellaOps.Provenance.Attestation.Tool/README.md
Normal file
34
src/Attestor/StellaOps.Provenance.Attestation.Tool/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# stella-forensic-verify (preview)
|
||||
|
||||
Minimal .NET 10 global tool for offline verification of provenance payloads signed with an HMAC key. No network access; deterministic JSON output.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
stella-forensic-verify \
|
||||
--payload payload.bin # or '-' to read stdin
|
||||
--signature-hex DEADBEEF... # hex-encoded HMAC
|
||||
--key-hex 001122... # hex-encoded HMAC key
|
||||
[--key-id hmac] # optional key id
|
||||
[--content-type application/octet-stream]
|
||||
[--signed-at 2025-11-21T12:00:00Z]
|
||||
[--not-after 2025-12-31T23:59:59Z]
|
||||
[--max-skew-minutes 5]
|
||||
```
|
||||
|
||||
Output (single line, deterministic field order):
|
||||
|
||||
```
|
||||
{"valid":true,"reason":"verified","verifiedAt":"2025-11-22T12:00:00.0000000Z","keyId":"hmac","contentType":"application/octet-stream"}
|
||||
```
|
||||
|
||||
## Exit codes
|
||||
- 0: signature valid
|
||||
- 2: signature/time invalid
|
||||
- 1: bad arguments or hex parse failure
|
||||
|
||||
## Offline kit packaging (manual)
|
||||
1. `dotnet pack src/Provenance/StellaOps.Provenance.Attestation.Tool/StellaOps.Provenance.Attestation.Tool.csproj -c Release -o out/tools`
|
||||
2. Copy the produced nupkg into the offline kit under `tools/`.
|
||||
3. Install in air-gap host: `dotnet tool install --global --add-source tools stella-forensic-verify --version <pkg-version>`.
|
||||
4. Document expected SHA256 of the nupkg alongside the kit manifest.
|
||||
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<OutputType>Exe</OutputType>
|
||||
<PackAsTool>true</PackAsTool>
|
||||
<ToolCommandName>stella-forensic-verify</ToolCommandName>
|
||||
<PackageOutputPath>../../out/tools</PackageOutputPath>
|
||||
<!-- Clear restore sources to use only explicit feeds (from deleted Directory.Build.props) -->
|
||||
<RestoreSources>;;</RestoreSources>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="StellaOps.Provenance.Attestation.Tests" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../StellaOps.Provenance.Attestation/StellaOps.Provenance.Attestation.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
# StellaOps.Provenance.Attestation.Tool Task Board
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/SPRINT_20260130_002_Tools_csproj_remediation_solid_review.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| REMED-05 | TODO | Remediation checklist: docs/implplan/audits/csproj-standards/remediation/checklists/src/Provenance/StellaOps.Provenance.Attestation.Tool/StellaOps.Provenance.Attestation.Tool.md. |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
@@ -0,0 +1 @@
|
||||
test
|
||||
Reference in New Issue
Block a user