Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<RestoreSources>;;</RestoreSources>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -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>
|
||||
|
||||
Binary file not shown.
@@ -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)
|
||||
|
||||
Binary file not shown.
@@ -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>
|
||||
Binary file not shown.
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
믯疿楳杮匠獹整敔瑸഻甊楳杮匠整汬佡獰倮潲敶慮据瑁整瑳瑡潩㭮獵湩畘楮㭴獵湩瑓汥慬灏敔瑳楋㭴慮敭灳捡瑓汥慬灏牐癯湥湡散䄮瑴獥慴楴湯吮獥獴഻ഊ瀊扵楬敳污摥挠慬獳吠潯䕬瑮祲潰湩呴獥獴ൻ †嬠牔楡⡴䌢瑡来牯≹敔瑳慃整潧楲獥售楮⥴൝ †嬠慆瑣൝ †瀠扵楬獡湹慔歳删湵獁湹彣敒畴湲䥳癮污摩湏楍獳湩䅧杲⡳ഩ †笠††††慶潣敤㴠愠慷瑩吠潯䕬瑮祲潰湩畒䅮祳据䄨牲祡䔮灭祴猼牴湩㹧⤨敔瑸牗瑩牥丮汵ⱬ渠睥匠牴湩坧楲整⡲Ⱙ渠睥吠獥呴浩健潲楶敤⡲慄整楔敭晏獦瑥售捴潎⥷㬩††††獁敳瑲䔮畱污ㄨ潣敤㬩††ൽഊ †嬠牔楡⡴䌢瑡来牯≹敔瑳慃整潧楲獥售楮⥴൝ †嬠慆瑣൝ †瀠扵楬獡湹慔歳删湵獁湹彣敖楲楦獥慖楬卤杩慮畴敲⤨††ൻ †††瘠牡瀠祡潬摡㴠䔠据摯湩呕㡆䜮瑥祂整⡳瀢祡潬摡⤢഻ †††瘠牡欠祥㴠䌠湯敶瑲吮䡯硥瑓楲杮䔨据摯湩呕㡆䜮瑥祂整⡳猢捥敲≴⤩഻ †††甠楳杮瘠牡栠慭‽敮⁷祓瑳浥匮捥牵瑩牃灹潴牧灡票䠮䅍千䅈㔲⠶湅潣楤杮售䙔⸸敇䉴瑹獥∨敳牣瑥⤢㬩††††慶楳‽潃癮牥潔效卸牴湩⡧浨捡䌮浯異整慈桳瀨祡潬摡⤩഻ഊ †††瘠牡琠灭㴠倠瑡敇呴浥䙰汩乥浡⡥㬩††††睡楡⁴楆敬圮楲整汁䉬瑹獥獁湹⡣浴Ɒ瀠祡潬摡㬩††††慶瑳潤瑵㴠渠睥匠牴湩坧楲整⡲㬩††††慶潣敤㴠愠慷瑩吠潯䕬瑮祲潰湩畒䅮祳据渨睥嵛††††ൻ †††††∠ⴭ慰汹慯≤浴Ɒ††††††ⴢ猭杩慮畴敲栭硥Ⱒ猠杩ബ †††††∠ⴭ敫敨≸敫ⱹ††††††ⴢ猭杩敮ⵤ瑡Ⱒ∠〲㔲ㄭⴱ㈲ご㨰〰〺娰ഢ †††素瑳潤瑵敮⁷瑓楲杮牗瑩牥⤨敮⁷敔瑳楔敭牐癯摩牥渨睥䐠瑡呥浩佥晦敳⡴〲㔲ㄬⰱ㈲〬〬〬听浩卥慰敚潲⤩㬩††††獁敳瑲䔮畱污〨潣敤㬩††††獁敳瑲䌮湯慴湩⡳尢瘢污摩≜琺畲≥瑳潤瑵吮卯牴湩⡧⤩഻ †素ൽ
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
믯疿楳杮匠獹整敔瑸഻甊楳杮匠整汬佡獰倮潲敶慮据瑁整瑳瑡潩㭮獵湩畘楮㭴獵湩瑓汥慬灏敔瑳楋㭴慮敭灳捡瑓汥慬灏牐癯湥湡散䄮瑴獥慴楴湯吮獥獴഻ഊ瀊扵楬敳污摥挠慬獳嘠牥晩捩瑡潩䱮扩慲祲敔瑳൳笊††呛慲瑩∨慃整潧祲Ⱒ吠獥䍴瑡来牯敩湕瑩崩††††䙛捡嵴††異汢捩愠祳据吠獡浈捡敖楲楦牥䙟楡獬桗湥敋䕹灸物摥⤨††ൻ †††瘠牡欠祥㴠渠睥䤠䵮浥牯䭹祥牐癯摩牥∨ㅫⰢ䔠据摯湩呕㡆䜮瑥祂整⡳猢捥敲≴Ⱙ䐠瑡呥浩佥晦敳瑕乣睯䄮摤楍畮整⡳ㄭ⤩഻ †††瘠牡瘠牥晩敩‽敮⁷浈捡敖楲楦牥欨祥敮⁷敔瑳楔敭牐癯摩牥䐨瑡呥浩佥晦敳瑕乣睯⤩഻ഊ †††瘠牡爠煥敵瑳㴠渠睥匠杩剮煥敵瑳䔨据摯湩呕㡆䜮瑥祂整⡳瀢祡潬摡⤢挢≴㬩††††慶楳湧牥㴠渠睥䠠慭卣杩敮⡲敫ⱹ琠浩健潲楶敤㩲渠睥吠獥呴浩健潲楶敤⡲慄整楔敭晏獦瑥售捴潎摁䵤湩瑵獥⤲⤩഻ †††瘠牡猠杩慮畴敲㴠愠慷瑩猠杩敮楓湧獁湹⡣敲畱獥⥴഻ഊ †††瘠牡爠獥汵⁴‽睡楡⁴敶楲楦牥嘮牥晩䅹祳据爨煥敵瑳楳湧瑡牵⥥഻ഊ †††䄠獳牥慆獬⡥敲畳瑬䤮噳污摩㬩††††獁敳瑲䌮湯慴湩⡳琢浩≥敲畳瑬刮慥潳⥮഻ †素††呛慲瑩∨慃整潧祲Ⱒ吠獥䍴瑡来牯敩湕瑩崩††††䙛捡嵴††異汢捩愠祳据吠獡浈捡敖楲楦牥䙟楡獬桗湥汃捯卫敫呷潯慌杲⡥ഩ †笠††††慶潮⁷‽敮⁷慄整楔敭晏獦瑥㈨㈰ⰵㄠⰱ㈠ⰲㄠⰲ〠ⰰ吠浩卥慰敚潲㬩††††慶敫⁹‽敮⁷湉敍潭祲敋偹潲楶敤⡲欢Ⱒ䔠据摯湩呕㡆䜮瑥祂整⡳猢捥敲≴⤩഻ †††瘠牡猠杩敮‽敮⁷浈捡楓湧牥欨祥楴敭牐癯摩牥›敮⁷敔瑳楔敭牐癯摩牥渨睯䄮摤楍畮整⡳〱⤩㬩††††慶敲畱獥⁴‽敮⁷楓湧敒畱獥⡴湅潣楤杮售䙔⸸敇䉴瑹獥∨慰汹慯≤Ⱙ∠瑣⤢഻ †††瘠牡猠杩㴠愠慷瑩猠杩敮楓湧獁湹⡣敲畱獥⥴഻ഊ †††瘠牡瘠牥晩敩‽敮⁷浈捡敖楲楦牥欨祥敮⁷敔瑳楔敭牐癯摩牥渨睯Ⱙ吠浩卥慰牆浯楍畮整⡳⤵㬩††††慶敲畳瑬㴠愠慷瑩瘠牥晩敩敖楲祦獁湹⡣敲畱獥ⱴ猠杩㬩††††獁敳瑲䘮污敳爨獥汵獉慖楬⥤഻ †素††呛慲瑩∨慃整潧祲Ⱒ吠獥䍴瑡来牯敩湕瑩崩††††䙛捡嵴††異汢捩瘠楯敍歲敬潒瑯敖楲楦牥䑟瑥捥獴楍浳瑡档⤨††ൻ †††瘠牡氠慥敶‽敮孷൝ †††笠††††††湅潣楤杮售䙔⸸敇䉴瑹獥∨≡Ⱙ††††††湅潣楤杮售䙔⸸敇䉴瑹獥∨≢Ⱙ††††††湅潣楤杮售䙔⸸敇䉴瑹獥∨≣ഩ †††素഻ †††瘠牡攠灸捥整‽潃癮牥牆浯效卸牴湩⡧〢∰㬩††††慶敲畳瑬㴠䴠牥汫剥潯噴牥晩敩敖楲祦潒瑯氨慥敶ⱳ攠灸捥整Ɽ渠睥吠獥呴浩健潲楶敤⡲慄整楔敭晏獦瑥售捴潎⥷㬩††††獁敳瑲䘮污敳爨獥汵獉慖楬⥤഻ †††䄠獳牥煅慵⡬洢牥汫潲瑯洠獩慭捴≨敲畳瑬刮慥潳⥮഻ †素††呛慲瑩∨慃整潧祲Ⱒ吠獥䍴瑡来牯敩湕瑩崩††††䙛捡嵴††異汢捩瘠楯桃楡佮䍦獵潴祤敖楲楦牥䍟浯異整䅳杧敲慧整⤨††ൻ †††瘠牡栠灯‽敮孷൝ †††笠††††††湅潣楤杮售䙔⸸敇䉴瑹獥∨潨ㅰ⤢ബ †††††䔠据摯湩呕㡆䜮瑥祂整⡳栢灯∲ഩ †††素഻ഊ †††甠楳杮瘠牡猠慨㴠匠獹整敓畣楲祴䌮祲瑰杯慲桰䡓㉁㘵䌮敲瑡⡥㬩††††慶条牧来瑡‽桳潃灭瑵䡥獡⡨牁慲浅瑰㱹祢整⠾⸩潃据瑡栨灯孳崰⸩潔牁慲⡹⤩഻ †††愠杧敲慧整㴠猠慨䌮浯異整慈桳愨杧敲慧整䌮湯慣⡴潨獰ㅛ⥝吮䅯牲祡⤨㬩††††慶敲畳瑬㴠䌠慨湩晏畃瑳摯噹牥晩敩敖楲祦栨灯ⱳ愠杧敲慧整敮⁷敔瑳楔敭牐癯摩牥䐨瑡呥浩佥晦敳瑕乣睯⤩഻ †††䄠獳牥牔敵爨獥汵獉慖楬⥤഻ †素ൽ
|
||||
Reference in New Issue
Block a user