Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -1,5 +0,0 @@
<Project>
<PropertyGroup>
<RestoreSources>;;</RestoreSources>
</PropertyGroup>
</Project>

View File

@@ -8,7 +8,12 @@
<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>

View File

@@ -6,6 +6,7 @@ using System.Text.Json;
using FluentAssertions;
using StellaOps.Cryptography;
using StellaOps.Provenance.Attestation;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Provenance.Attestation.Tests;
@@ -58,7 +59,7 @@ public class SampleStatementDigestTests
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public void Hashes_match_expected_samples()
{
// Expected hashes using FIPS profile (SHA-256 for attestation purpose)
@@ -70,7 +71,6 @@ public class SampleStatementDigestTests
["orchestrator-statement.json"] = "d79467d03da33d0b8f848d7a340c8cde845802bad7dadcb553125e8553615b28"
};
using StellaOps.TestKit;
foreach (var (name, statement) in LoadSamples())
{
BuildStatementDigest.ComputeHashHex(_cryptoHash, statement)

View File

@@ -1,19 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../StellaOps.Provenance.Attestation/StellaOps.Provenance.Attestation.csproj" />
<ProjectReference Include="../../StellaOps.Provenance.Attestation.Tool/StellaOps.Provenance.Attestation.Tool.csproj" />
<ProjectReference Include="../../../../src/__Libraries/StellaOps.Cryptography/StellaOps.Cryptography.csproj" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<ProjectReference Include="../../../../src/__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>
</Project>
</Project>

View File

@@ -0,0 +1,43 @@
using System.Text;
using StellaOps.Provenance.Attestation;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Provenance.Attestation.Tests;
public sealed class ToolEntrypointTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RunAsync_ReturnsInvalidOnMissingArgs()
{
var code = await ToolEntrypoint.RunAsync(Array.Empty<string>(), TextWriter.Null, new StringWriter(), new TestTimeProvider(DateTimeOffset.UtcNow));
Assert.Equal(1, code);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task RunAsync_VerifiesValidSignature()
{
var payload = Encoding.UTF8.GetBytes("payload");
var key = Convert.ToHexString(Encoding.UTF8.GetBytes("secret"));
using var hmac = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes("secret"));
var sig = Convert.ToHexString(hmac.ComputeHash(payload));
var tmp = Path.GetTempFileName();
await File.WriteAllBytesAsync(tmp, payload);
var stdout = new StringWriter();
var code = await ToolEntrypoint.RunAsync(new[]
{
"--payload", tmp,
"--signature-hex", sig,
"--key-hex", key,
"--signed-at", "2025-11-22T00:00:00Z"
}, stdout, new StringWriter(), new TestTimeProvider(new DateTimeOffset(2025,11,22,0,0,0,TimeSpan.Zero)));
Assert.Equal(0, code);
Assert.Contains("\"valid\":true", stdout.ToString());
}
}

View File

@@ -0,0 +1 @@
믯疿楳杮匠獹整⹭敔瑸഻甊楳杮匠整汬佡獰倮潲敶慮据⹥瑁整瑳瑡潩㭮਍獵湩⁧畘楮㭴਍਍਍獵湩⁧瑓汥慬灏⹳敔瑳楋㭴਍慮敭灳捡⁥瑓汥慬灏⹳牐癯湥湡散䄮瑴獥慴楴湯吮獥獴഻ഊ瀊扵楬⁣敳污摥挠慬獳吠潯䕬瑮祲潰湩呴獥獴਍ൻ †嬠牔楡⡴䌢瑡来牯≹‬敔瑳慃整潧楲獥售楮⥴൝ †嬠慆瑣൝ †瀠扵楬⁣獡湹⁣慔歳删湵獁湹彣敒畴湲䥳癮污摩湏楍獳湩䅧杲⡳ഩ †笠਍††††慶⁲潣敤㴠愠慷瑩吠潯䕬瑮祲潰湩⹴畒䅮祳据䄨牲祡䔮灭祴猼牴湩㹧⤨‬敔瑸牗瑩牥丮汵ⱬ渠睥匠牴湩坧楲整⡲Ⱙ渠睥吠獥呴浩健潲楶敤⡲慄整楔敭晏獦瑥售捴潎⥷㬩਍††††獁敳瑲䔮畱污ㄨ‬潣敤㬩਍††ൽഊ †嬠牔楡⡴䌢瑡来牯≹‬敔瑳慃整潧楲獥售楮⥴൝ †嬠慆瑣൝ †瀠扵楬⁣獡湹⁣慔歳删湵獁湹彣敖楲楦獥慖楬卤杩慮畴敲⤨਍††ൻ †††瘠牡瀠祡潬摡㴠䔠据摯湩⹧呕㡆䜮瑥祂整⡳瀢祡潬摡⤢഻ †††瘠牡欠祥㴠䌠湯敶瑲吮䡯硥瑓楲杮䔨据摯湩⹧呕㡆䜮瑥祂整⡳猢捥敲≴⤩഻ †††甠楳杮瘠牡栠慭⁣‽敮⁷祓瑳浥匮捥牵瑩⹹牃灹潴牧灡票䠮䅍千䅈㔲⠶湅潣楤杮售䙔⸸敇䉴瑹獥∨敳牣瑥⤢㬩਍††††慶⁲楳⁧‽潃癮牥⹴潔效卸牴湩⡧浨捡䌮浯異整慈桳瀨祡潬摡⤩഻ഊ †††瘠牡琠灭㴠倠瑡⹨敇呴浥䙰汩乥浡⡥㬩਍††††睡楡⁴楆敬圮楲整汁䉬瑹獥獁湹⡣浴Ɒ瀠祡潬摡㬩਍਍††††慶⁲瑳潤瑵㴠渠睥匠牴湩坧楲整⡲㬩਍††††慶⁲潣敤㴠愠慷瑩吠潯䕬瑮祲潰湩⹴畒䅮祳据渨睥嵛਍††††ൻ †††††∠ⴭ慰汹慯≤‬浴Ɒ਍††††††ⴢ猭杩慮畴敲栭硥Ⱒ猠杩ബ †††††∠ⴭ敫⵹敨≸‬敫ⱹ਍††††††ⴢ猭杩敮ⵤ瑡Ⱒ∠〲㔲ㄭⴱ㈲ご㨰〰〺娰ഢ †††素‬瑳潤瑵‬敮⁷瑓楲杮牗瑩牥⤨‬敮⁷敔瑳楔敭牐癯摩牥渨睥䐠瑡呥浩佥晦敳⡴〲㔲ㄬⰱ㈲〬〬〬听浩卥慰⹮敚潲⤩㬩਍਍††††獁敳瑲䔮畱污〨‬潣敤㬩਍††††獁敳瑲䌮湯慴湩⡳尢瘢污摩≜琺畲≥‬瑳潤瑵吮卯牴湩⡧⤩഻ †素਍ൽ

View File

@@ -0,0 +1,123 @@
using System.Text;
using StellaOps.Cryptography;
using StellaOps.Provenance.Attestation;
using Xunit;
using StellaOps.TestKit;
namespace StellaOps.Provenance.Attestation.Tests;
public sealed class VerificationLibraryTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HmacVerifier_FailsWhenKeyExpired()
{
var key = new InMemoryKeyProvider("k1", Encoding.UTF8.GetBytes("secret"), DateTimeOffset.UtcNow.AddMinutes(-1));
var verifier = new HmacVerifier(key, new TestTimeProvider(DateTimeOffset.UtcNow));
var request = new SignRequest(Encoding.UTF8.GetBytes("payload"), "ct");
var signer = new HmacSigner(key, new FakeCryptoHmac(), timeProvider: new TestTimeProvider(DateTimeOffset.UtcNow.AddMinutes(-2)));
var signature = await signer.SignAsync(request);
var result = await verifier.VerifyAsync(request, signature);
Assert.False(result.IsValid);
Assert.Contains("time", result.Reason);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public async Task HmacVerifier_FailsWhenClockSkewTooLarge()
{
var now = new DateTimeOffset(2025, 11, 22, 12, 0, 0, TimeSpan.Zero);
var key = new InMemoryKeyProvider("k", Encoding.UTF8.GetBytes("secret"));
var signer = new HmacSigner(key, new FakeCryptoHmac(), timeProvider: new TestTimeProvider(now.AddMinutes(10)));
var request = new SignRequest(Encoding.UTF8.GetBytes("payload"), "ct");
var sig = await signer.SignAsync(request);
var verifier = new HmacVerifier(key, new TestTimeProvider(now), TimeSpan.FromMinutes(5));
var result = await verifier.VerifyAsync(request, sig);
Assert.False(result.IsValid);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void MerkleRootVerifier_DetectsMismatch()
{
var leaves = new[]
{
Encoding.UTF8.GetBytes("a"),
Encoding.UTF8.GetBytes("b"),
Encoding.UTF8.GetBytes("c")
};
var expected = Convert.FromHexString("00");
var cryptoHash = DefaultCryptoHash.CreateForTests();
var result = MerkleRootVerifier.VerifyRoot(cryptoHash, leaves, expected, new TestTimeProvider(DateTimeOffset.UtcNow));
Assert.False(result.IsValid);
Assert.Equal("merkle root mismatch", result.Reason);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void ChainOfCustodyVerifier_ComputesAggregate()
{
var hops = new[]
{
Encoding.UTF8.GetBytes("hop1"),
Encoding.UTF8.GetBytes("hop2")
};
var cryptoHash = DefaultCryptoHash.CreateForTests();
using var sha = System.Security.Cryptography.SHA256.Create();
var aggregate = sha.ComputeHash(Array.Empty<byte>().Concat(hops[0]).ToArray());
aggregate = sha.ComputeHash(aggregate.Concat(hops[1]).ToArray());
var result = ChainOfCustodyVerifier.Verify(cryptoHash, hops, aggregate, new TestTimeProvider(DateTimeOffset.UtcNow));
Assert.True(result.IsValid);
}
private sealed class FakeCryptoHmac : ICryptoHmac
{
public byte[] ComputeHmacForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string purpose)
{
using var hmac = new System.Security.Cryptography.HMACSHA256(key.ToArray());
return hmac.ComputeHash(data.ToArray());
}
public string ComputeHmacHexForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string purpose)
=> Convert.ToHexStringLower(ComputeHmacForPurpose(key, data, purpose));
public string ComputeHmacBase64ForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string purpose)
=> Convert.ToBase64String(ComputeHmacForPurpose(key, data, purpose));
public async ValueTask<byte[]> ComputeHmacForPurposeAsync(ReadOnlyMemory<byte> key, Stream stream, string purpose, CancellationToken cancellationToken = default)
{
using var ms = new MemoryStream();
await stream.CopyToAsync(ms, cancellationToken);
return ComputeHmacForPurpose(key.Span, ms.ToArray(), purpose);
}
public async ValueTask<string> ComputeHmacHexForPurposeAsync(ReadOnlyMemory<byte> key, Stream stream, string purpose, CancellationToken cancellationToken = default)
=> Convert.ToHexStringLower(await ComputeHmacForPurposeAsync(key, stream, purpose, cancellationToken));
public async ValueTask<string> ComputeHmacBase64ForPurposeAsync(ReadOnlyMemory<byte> key, Stream stream, string purpose, CancellationToken cancellationToken = default)
=> Convert.ToBase64String(await ComputeHmacForPurposeAsync(key, stream, purpose, cancellationToken));
public bool VerifyHmacForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, ReadOnlySpan<byte> expectedHmac, string purpose)
=> ComputeHmacForPurpose(key, data, purpose).AsSpan().SequenceEqual(expectedHmac);
public bool VerifyHmacHexForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string expectedHmacHex, string purpose)
=> ComputeHmacHexForPurpose(key, data, purpose).Equals(expectedHmacHex, StringComparison.OrdinalIgnoreCase);
public bool VerifyHmacBase64ForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string expectedHmacBase64, string purpose)
=> ComputeHmacBase64ForPurpose(key, data, purpose).Equals(expectedHmacBase64, StringComparison.Ordinal);
public string GetAlgorithmForPurpose(string purpose) => "HMACSHA256";
public int GetOutputLengthForPurpose(string purpose) => 32;
}
}

View File

@@ -0,0 +1 @@
믯疿楳杮匠獹整⹭敔瑸഻甊楳杮匠整汬佡獰倮潲敶慮据⹥瑁整瑳瑡潩㭮਍獵湩⁧畘楮㭴਍਍਍獵湩⁧瑓汥慬灏⹳敔瑳楋㭴਍慮敭灳捡⁥瑓汥慬灏⹳牐癯湥湡散䄮瑴獥慴楴湯吮獥獴഻ഊ瀊扵楬⁣敳污摥挠慬獳嘠牥晩捩瑡潩䱮扩慲祲敔瑳൳笊਍††呛慲瑩∨慃整潧祲Ⱒ吠獥䍴瑡来牯敩⹳湕瑩崩਍††††䙛捡嵴਍††異汢捩愠祳据吠獡浈捡敖楲楦牥䙟楡獬桗湥敋䕹灸物摥⤨਍††ൻ †††瘠牡欠祥㴠渠睥䤠䵮浥牯䭹祥牐癯摩牥∨ㅫⰢ䔠据摯湩⹧呕㡆䜮瑥祂整⡳猢捥敲≴Ⱙ䐠瑡呥浩佥晦敳⹴瑕乣睯䄮摤楍畮整⡳ㄭ⤩഻ †††瘠牡瘠牥晩敩⁲‽敮⁷浈捡敖楲楦牥欨祥‬敮⁷敔瑳楔敭牐癯摩牥䐨瑡呥浩佥晦敳⹴瑕乣睯⤩഻ഊ †††瘠牡爠煥敵瑳㴠渠睥匠杩剮煥敵瑳䔨据摯湩⹧呕㡆䜮瑥祂整⡳瀢祡潬摡⤢‬挢≴㬩਍††††慶⁲楳湧牥㴠渠睥䠠慭卣杩敮⡲敫ⱹ琠浩健潲楶敤㩲渠睥吠獥呴浩健潲楶敤⡲慄整楔敭晏獦瑥售捴潎⹷摁䵤湩瑵獥⴨⤲⤩഻ †††瘠牡猠杩慮畴敲㴠愠慷瑩猠杩敮⹲楓湧獁湹⡣敲畱獥⥴഻ഊ †††瘠牡爠獥汵⁴‽睡楡⁴敶楲楦牥嘮牥晩䅹祳据爨煥敵瑳‬楳湧瑡牵⥥഻ഊ †††䄠獳牥⹴慆獬⡥敲畳瑬䤮噳污摩㬩਍††††獁敳瑲䌮湯慴湩⡳琢浩≥‬敲畳瑬刮慥潳⥮഻ †素਍਍††呛慲瑩∨慃整潧祲Ⱒ吠獥䍴瑡来牯敩⹳湕瑩崩਍††††䙛捡嵴਍††異汢捩愠祳据吠獡浈捡敖楲楦牥䙟楡獬桗湥汃捯卫敫呷潯慌杲⡥ഩ †笠਍††††慶⁲潮⁷‽敮⁷慄整楔敭晏獦瑥㈨㈰ⰵㄠⰱ㈠ⰲㄠⰲ〠‬ⰰ吠浩卥慰⹮敚潲㬩਍††††慶⁲敫⁹‽敮⁷湉敍潭祲敋偹潲楶敤⡲欢Ⱒ䔠据摯湩⹧呕㡆䜮瑥祂整⡳猢捥敲≴⤩഻ †††瘠牡猠杩敮⁲‽敮⁷浈捡楓湧牥欨祥‬楴敭牐癯摩牥›敮⁷敔瑳楔敭牐癯摩牥渨睯䄮摤楍畮整⡳〱⤩㬩਍††††慶⁲敲畱獥⁴‽敮⁷楓湧敒畱獥⡴湅潣楤杮售䙔⸸敇䉴瑹獥∨慰汹慯≤Ⱙ∠瑣⤢഻ †††瘠牡猠杩㴠愠慷瑩猠杩敮⹲楓湧獁湹⡣敲畱獥⥴഻ഊ †††瘠牡瘠牥晩敩⁲‽敮⁷浈捡敖楲楦牥欨祥‬敮⁷敔瑳楔敭牐癯摩牥渨睯Ⱙ吠浩卥慰⹮牆浯楍畮整⡳⤵㬩਍††††慶⁲敲畳瑬㴠愠慷瑩瘠牥晩敩⹲敖楲祦獁湹⡣敲畱獥ⱴ猠杩㬩਍਍††††獁敳瑲䘮污敳爨獥汵⹴獉慖楬⥤഻ †素਍਍††呛慲瑩∨慃整潧祲Ⱒ吠獥䍴瑡来牯敩⹳湕瑩崩਍††††䙛捡嵴਍††異汢捩瘠楯⁤敍歲敬潒瑯敖楲楦牥䑟瑥捥獴楍浳瑡档⤨਍††ൻ †††瘠牡氠慥敶⁳‽敮孷൝ †††笠਍††††††湅潣楤杮售䙔⸸敇䉴瑹獥∨≡Ⱙ਍††††††湅潣楤杮售䙔⸸敇䉴瑹獥∨≢Ⱙ਍††††††湅潣楤杮售䙔⸸敇䉴瑹獥∨≣ഩ †††素഻ †††瘠牡攠灸捥整⁤‽潃癮牥⹴牆浯效卸牴湩⡧〢∰㬩਍਍††††慶⁲敲畳瑬㴠䴠牥汫剥潯噴牥晩敩⹲敖楲祦潒瑯氨慥敶ⱳ攠灸捥整Ɽ渠睥吠獥呴浩健潲楶敤⡲慄整楔敭晏獦瑥售捴潎⥷㬩਍਍††††獁敳瑲䘮污敳爨獥汵⹴獉慖楬⥤഻ †††䄠獳牥⹴煅慵⡬洢牥汫⁥潲瑯洠獩慭捴≨‬敲畳瑬刮慥潳⥮഻ †素਍਍††呛慲瑩∨慃整潧祲Ⱒ吠獥䍴瑡来牯敩⹳湕瑩崩਍††††䙛捡嵴਍††異汢捩瘠楯⁤桃楡佮䍦獵潴祤敖楲楦牥䍟浯異整䅳杧敲慧整⤨਍††ൻ †††瘠牡栠灯⁳‽敮孷൝ †††笠਍††††††湅潣楤杮售䙔⸸敇䉴瑹獥∨潨ㅰ⤢ബ †††††䔠据摯湩⹧呕㡆䜮瑥祂整⡳栢灯∲ഩ †††素഻ഊ †††甠楳杮瘠牡猠慨㴠匠獹整⹭敓畣楲祴䌮祲瑰杯慲桰⹹䡓㉁㘵䌮敲瑡⡥㬩਍††††慶⁲条牧来瑡⁥‽桳⹡潃灭瑵䡥獡⡨牁慲⹹浅瑰㱹祢整⠾⸩潃据瑡栨灯孳崰⸩潔牁慲⡹⤩഻ †††愠杧敲慧整㴠猠慨䌮浯異整慈桳愨杧敲慧整䌮湯慣⡴潨獰ㅛ⥝吮䅯牲祡⤨㬩਍਍††††慶⁲敲畳瑬㴠䌠慨湩晏畃瑳摯噹牥晩敩⹲敖楲祦栨灯ⱳ愠杧敲慧整‬敮⁷敔瑳楔敭牐癯摩牥䐨瑡呥浩佥晦敳⹴瑕乣睯⤩഻ †††䄠獳牥⹴牔敵爨獥汵⹴獉慖楬⥤഻ †素਍ൽ