Add new features and tests for AirGap and Time modules
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Introduced `SbomService` tasks documentation. - Updated `StellaOps.sln` to include new projects: `StellaOps.AirGap.Time` and `StellaOps.AirGap.Importer`. - Added unit tests for `BundleImportPlanner`, `DsseVerifier`, `ImportValidator`, and other components in the `StellaOps.AirGap.Importer.Tests` namespace. - Implemented `InMemoryBundleRepositories` for testing bundle catalog and item repositories. - Created `MerkleRootCalculator`, `RootRotationPolicy`, and `TufMetadataValidator` tests. - Developed `StalenessCalculator` and `TimeAnchorLoader` tests in the `StellaOps.AirGap.Time.Tests` namespace. - Added `fetch-sbomservice-deps.sh` script for offline dependency fetching.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using StellaOps.AirGap.Time.Services;
|
||||
|
||||
namespace StellaOps.AirGap.Time.Tests;
|
||||
|
||||
public class StalenessCalculatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void UnknownWhenNoAnchor()
|
||||
{
|
||||
var calc = new StalenessCalculator();
|
||||
var result = calc.Evaluate(TimeAnchor.Unknown, StalenessBudget.Default, DateTimeOffset.UnixEpoch);
|
||||
Assert.False(result.IsWarning);
|
||||
Assert.False(result.IsBreach);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BreachWhenBeyondBudget()
|
||||
{
|
||||
var anchor = new TimeAnchor(DateTimeOffset.UnixEpoch, "source", "fmt", "fp", "digest");
|
||||
var budget = new StalenessBudget(10, 20);
|
||||
var calc = new StalenessCalculator();
|
||||
|
||||
var result = calc.Evaluate(anchor, budget, DateTimeOffset.UnixEpoch.AddSeconds(25));
|
||||
|
||||
Assert.True(result.IsBreach);
|
||||
Assert.True(result.IsWarning);
|
||||
Assert.Equal(25, result.AgeSeconds);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WarningWhenBetweenWarningAndBreach()
|
||||
{
|
||||
var anchor = new TimeAnchor(DateTimeOffset.UnixEpoch, "source", "fmt", "fp", "digest");
|
||||
var budget = new StalenessBudget(10, 20);
|
||||
var calc = new StalenessCalculator();
|
||||
|
||||
var result = calc.Evaluate(anchor, budget, DateTimeOffset.UnixEpoch.AddSeconds(15));
|
||||
|
||||
Assert.True(result.IsWarning);
|
||||
Assert.False(result.IsBreach);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../src/AirGap/StellaOps.AirGap.Time/StellaOps.AirGap.Time.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,28 @@
|
||||
using StellaOps.AirGap.Time.Parsing;
|
||||
using StellaOps.AirGap.Time.Services;
|
||||
|
||||
namespace StellaOps.AirGap.Time.Tests;
|
||||
|
||||
public class TimeAnchorLoaderTests
|
||||
{
|
||||
[Fact]
|
||||
public void RejectsInvalidHex()
|
||||
{
|
||||
var loader = new TimeAnchorLoader();
|
||||
var result = loader.TryLoadHex("not-hex", TimeTokenFormat.Roughtime, Array.Empty<TimeTrustRoot>(), out _);
|
||||
Assert.False(result.IsValid);
|
||||
Assert.Equal("token-hex-invalid", result.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadsHexToken()
|
||||
{
|
||||
var loader = new TimeAnchorLoader();
|
||||
var hex = "01020304";
|
||||
var trust = new[] { new TimeTrustRoot("k1", new byte[] { 0x01 }, "rsassa-pss-sha256") };
|
||||
var result = loader.TryLoadHex(hex, TimeTokenFormat.Roughtime, trust, out var anchor);
|
||||
|
||||
Assert.True(result.IsValid);
|
||||
Assert.Equal("Roughtime", anchor.Format);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
|
||||
namespace StellaOps.AirGap.Time.Tests;
|
||||
|
||||
public class TimeStatusDtoTests
|
||||
{
|
||||
[Fact]
|
||||
public void SerializesDeterministically()
|
||||
{
|
||||
var status = new TimeStatus(
|
||||
new TimeAnchor(DateTimeOffset.Parse("2025-01-01T00:00:00Z"), "source", "fmt", "fp", "digest"),
|
||||
new StalenessEvaluation(42, 10, 20, true, false),
|
||||
new StalenessBudget(10, 20),
|
||||
DateTimeOffset.Parse("2025-01-02T00:00:00Z"));
|
||||
|
||||
var json = TimeStatusDto.FromStatus(status).ToJson();
|
||||
|
||||
Assert.Equal("{\"anchorTime\":\"2025-01-01T00:00:00.0000000Z\",\"format\":\"fmt\",\"source\":\"source\",\"fingerprint\":\"fp\",\"digest\":\"digest\",\"ageSeconds\":42,\"warningSeconds\":10,\"breachSeconds\":20,\"isWarning\":true,\"isBreach\":false,\"evaluatedAtUtc\":\"2025-01-02T00:00:00.0000000Z\"}", json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using StellaOps.AirGap.Time.Services;
|
||||
using StellaOps.AirGap.Time.Stores;
|
||||
|
||||
namespace StellaOps.AirGap.Time.Tests;
|
||||
|
||||
public class TimeStatusServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ReturnsUnknownWhenNoAnchor()
|
||||
{
|
||||
var svc = Build();
|
||||
var status = await svc.GetStatusAsync("t1", DateTimeOffset.UnixEpoch);
|
||||
Assert.Equal(TimeAnchor.Unknown, status.Anchor);
|
||||
Assert.False(status.Staleness.IsWarning);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PersistsAnchorAndBudget()
|
||||
{
|
||||
var svc = Build();
|
||||
var anchor = new TimeAnchor(DateTimeOffset.UnixEpoch, "source", "fmt", "fp", "digest");
|
||||
var budget = new StalenessBudget(10, 20);
|
||||
|
||||
await svc.SetAnchorAsync("t1", anchor, budget);
|
||||
var status = await svc.GetStatusAsync("t1", DateTimeOffset.UnixEpoch.AddSeconds(15));
|
||||
|
||||
Assert.Equal(anchor, status.Anchor);
|
||||
Assert.True(status.Staleness.IsWarning);
|
||||
Assert.False(status.Staleness.IsBreach);
|
||||
}
|
||||
|
||||
private static TimeStatusService Build() => new(new InMemoryTimeAnchorStore(), new StalenessCalculator());
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using StellaOps.AirGap.Time.Parsing;
|
||||
|
||||
namespace StellaOps.AirGap.Time.Tests;
|
||||
|
||||
public class TimeTokenParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void EmptyTokenFails()
|
||||
{
|
||||
var parser = new TimeTokenParser();
|
||||
var result = parser.TryParse(Array.Empty<byte>(), TimeTokenFormat.Roughtime, out var anchor);
|
||||
|
||||
Assert.False(result.IsValid);
|
||||
Assert.Equal("token-empty", result.Reason);
|
||||
Assert.Equal(TimeAnchor.Unknown, anchor);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RoughtimeTokenProducesDigest()
|
||||
{
|
||||
var parser = new TimeTokenParser();
|
||||
var token = new byte[] { 0x01, 0x02, 0x03 };
|
||||
|
||||
var result = parser.TryParse(token, TimeTokenFormat.Roughtime, out var anchor);
|
||||
|
||||
Assert.True(result.IsValid);
|
||||
Assert.Equal("Roughtime", anchor.Format);
|
||||
Assert.Equal("roughtime-token", anchor.Source);
|
||||
Assert.Equal("structure-stubbed", result.Reason);
|
||||
Assert.Matches("^[0-9a-f]{64}$", anchor.TokenDigest);
|
||||
Assert.NotEqual(DateTimeOffset.UnixEpoch, anchor.AnchorTime); // deterministic derivation
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using StellaOps.AirGap.Time.Models;
|
||||
using StellaOps.AirGap.Time.Parsing;
|
||||
using StellaOps.AirGap.Time.Services;
|
||||
|
||||
namespace StellaOps.AirGap.Time.Tests;
|
||||
|
||||
public class TimeVerificationServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void FailsWithoutTrustRoots()
|
||||
{
|
||||
var svc = new TimeVerificationService();
|
||||
var result = svc.Verify(new byte[] { 0x01 }, TimeTokenFormat.Roughtime, Array.Empty<TimeTrustRoot>(), out _);
|
||||
Assert.False(result.IsValid);
|
||||
Assert.Equal("trust-roots-required", result.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SucceedsForRoughtimeWithTrustRoot()
|
||||
{
|
||||
var svc = new TimeVerificationService();
|
||||
var trust = new[] { new TimeTrustRoot("k1", new byte[] { 0x01 }, "rsassa-pss-sha256") };
|
||||
var result = svc.Verify(new byte[] { 0x01, 0x02 }, TimeTokenFormat.Roughtime, trust, out var anchor);
|
||||
Assert.True(result.IsValid);
|
||||
Assert.Equal("Roughtime", anchor.Format);
|
||||
Assert.Equal("k1", anchor.SignatureFingerprint);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user