license switch agpl -> busl1, sprints work, new product advisories
This commit is contained in:
@@ -26,7 +26,8 @@
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.Configuration/StellaOps.Configuration.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.DependencyInjection/StellaOps.DependencyInjection.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Attestor.Verify\StellaOps.Attestor.Verify.csproj" />
|
||||
<ProjectReference Include="..\..\__Libraries\StellaOps.Attestor.Timestamping\StellaOps.Attestor.Timestamping.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography.Plugin.SmSoft\StellaOps.Cryptography.Plugin.SmSoft.csproj" />
|
||||
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -8,3 +8,7 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
||||
| AUDIT-0066-M | DONE | Revalidated 2026-01-06 (maintainability audit). |
|
||||
| AUDIT-0066-T | DONE | Revalidated 2026-01-06 (test coverage audit). |
|
||||
| AUDIT-0066-A | DONE | Waived (test project; revalidated 2026-01-06). |
|
||||
| ATT-001 | DONE | Timestamping service unit tests for envelope digest handling. |
|
||||
| ATT-002 | DONE | Timestamp verification scenarios covered. |
|
||||
| ATT-003 | DONE | Timestamp policy evaluator scenarios covered. |
|
||||
| ATT-006 | DONE | Time correlation validator unit tests added. |
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
using FluentAssertions;
|
||||
using StellaOps.Attestor.Timestamping;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.Tests.Timestamping;
|
||||
|
||||
public sealed class AttestationTimestampPolicyTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Evaluate_WhenRfc3161RequiredAndMissing_Fails()
|
||||
{
|
||||
var evaluator = new TimestampPolicyEvaluator();
|
||||
var context = new AttestationTimestampPolicyContext { HasValidTst = false };
|
||||
var policy = new TimestampPolicy { RequireRfc3161 = true };
|
||||
|
||||
var result = evaluator.Evaluate(context, policy);
|
||||
|
||||
result.IsCompliant.Should().BeFalse();
|
||||
result.Violations.Should().Contain(v => v.RuleId == "require-rfc3161");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Evaluate_WhenSkewExceedsThreshold_Fails()
|
||||
{
|
||||
var evaluator = new TimestampPolicyEvaluator();
|
||||
var context = new AttestationTimestampPolicyContext
|
||||
{
|
||||
HasValidTst = true,
|
||||
TimeSkew = TimeSpan.FromMinutes(10)
|
||||
};
|
||||
var policy = new TimestampPolicy
|
||||
{
|
||||
RequireRfc3161 = true,
|
||||
MaxTimeSkew = TimeSpan.FromMinutes(5)
|
||||
};
|
||||
|
||||
var result = evaluator.Evaluate(context, policy);
|
||||
|
||||
result.IsCompliant.Should().BeFalse();
|
||||
result.Violations.Should().Contain(v => v.RuleId == "time-skew");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Evaluate_WhenRevocationStapleMissing_Fails()
|
||||
{
|
||||
var evaluator = new TimestampPolicyEvaluator();
|
||||
var context = new AttestationTimestampPolicyContext
|
||||
{
|
||||
HasValidTst = true,
|
||||
OcspStatus = null,
|
||||
CrlChecked = false
|
||||
};
|
||||
var policy = new TimestampPolicy
|
||||
{
|
||||
RequireRfc3161 = true,
|
||||
RequireRevocationStapling = true
|
||||
};
|
||||
|
||||
var result = evaluator.Evaluate(context, policy);
|
||||
|
||||
result.IsCompliant.Should().BeFalse();
|
||||
result.Violations.Should().Contain(v => v.RuleId == "revocation-staple");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Evaluate_WhenTsaNotTrusted_Fails()
|
||||
{
|
||||
var evaluator = new TimestampPolicyEvaluator();
|
||||
var context = new AttestationTimestampPolicyContext
|
||||
{
|
||||
HasValidTst = true,
|
||||
TsaName = "Untrusted TSA",
|
||||
OcspStatus = "Good",
|
||||
CrlChecked = true
|
||||
};
|
||||
var policy = new TimestampPolicy
|
||||
{
|
||||
RequireRfc3161 = true,
|
||||
RequireRevocationStapling = true,
|
||||
TrustedTsas = new[] { "Trusted TSA" }
|
||||
};
|
||||
|
||||
var result = evaluator.Evaluate(context, policy);
|
||||
|
||||
result.IsCompliant.Should().BeFalse();
|
||||
result.Violations.Should().Contain(v => v.RuleId == "trusted-tsa");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Evaluate_WhenAllRequirementsMet_Passes()
|
||||
{
|
||||
var evaluator = new TimestampPolicyEvaluator();
|
||||
var context = new AttestationTimestampPolicyContext
|
||||
{
|
||||
HasValidTst = true,
|
||||
TimeSkew = TimeSpan.FromMinutes(1),
|
||||
TsaName = "Trusted TSA",
|
||||
OcspStatus = "Good",
|
||||
CrlChecked = true,
|
||||
TsaCertificateExpires = DateTimeOffset.UtcNow.AddDays(365)
|
||||
};
|
||||
var policy = new TimestampPolicy
|
||||
{
|
||||
RequireRfc3161 = true,
|
||||
RequireRevocationStapling = true,
|
||||
MaxTimeSkew = TimeSpan.FromMinutes(5),
|
||||
MinCertificateFreshness = TimeSpan.FromDays(180),
|
||||
TrustedTsas = new[] { "Trusted TSA" }
|
||||
};
|
||||
|
||||
var result = evaluator.Evaluate(context, policy);
|
||||
|
||||
result.IsCompliant.Should().BeTrue();
|
||||
result.Violations.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Attestor.Timestamping;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.Tests.Timestamping;
|
||||
|
||||
public sealed class AttestationTimestampServiceTests
|
||||
{
|
||||
private static AttestationTimestampService CreateService()
|
||||
{
|
||||
var options = Options.Create(new AttestationTimestampServiceOptions());
|
||||
return new AttestationTimestampService(options, NullLogger<AttestationTimestampService>.Instance);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task TimestampAsync_ComputesEnvelopeDigest()
|
||||
{
|
||||
var service = CreateService();
|
||||
var envelope = Encoding.UTF8.GetBytes("{\"payload\":\"test\"}");
|
||||
|
||||
var result = await service.TimestampAsync(
|
||||
envelope,
|
||||
new AttestationTimestampOptions { HashAlgorithm = "SHA256" });
|
||||
|
||||
var expectedHash = Convert.ToHexString(SHA256.HashData(envelope)).ToLowerInvariant();
|
||||
result.EnvelopeDigest.Should().Be($"sha256:{expectedHash}");
|
||||
result.Envelope.Should().Equal(envelope);
|
||||
result.TsaName.Should().NotBeNullOrWhiteSpace();
|
||||
result.TsaPolicyOid.Should().NotBeNullOrWhiteSpace();
|
||||
result.TimestampTime.Should().NotBe(default);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WithConsistentRekorTime_ReturnsSuccess()
|
||||
{
|
||||
var service = CreateService();
|
||||
var envelope = Encoding.UTF8.GetBytes("{\"payload\":\"test\"}");
|
||||
var digest = "sha256:" + Convert.ToHexString(SHA256.HashData(envelope)).ToLowerInvariant();
|
||||
var tstTime = new DateTimeOffset(2026, 1, 19, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
var attestation = new TimestampedAttestation
|
||||
{
|
||||
Envelope = envelope,
|
||||
EnvelopeDigest = digest,
|
||||
TimeStampToken = Array.Empty<byte>(),
|
||||
TimestampTime = tstTime,
|
||||
TsaName = "Test TSA",
|
||||
TsaPolicyOid = "1.2.3.4",
|
||||
RekorReceipt = new RekorReceipt
|
||||
{
|
||||
LogId = "rekor",
|
||||
LogIndex = 42,
|
||||
IntegratedTime = tstTime.AddMinutes(1)
|
||||
}
|
||||
};
|
||||
|
||||
var options = new AttestationTimestampVerificationOptions
|
||||
{
|
||||
RequireRekorConsistency = true,
|
||||
MaxTimeSkew = TimeSpan.FromMinutes(5),
|
||||
VerifyTsaRevocation = false
|
||||
};
|
||||
|
||||
var result = await service.VerifyAsync(attestation, options);
|
||||
|
||||
result.IsValid.Should().BeTrue();
|
||||
result.TimeConsistency.Should().NotBeNull();
|
||||
result.TimeConsistency!.WithinTolerance.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WithRekorInconsistency_ReturnsFailure()
|
||||
{
|
||||
var service = CreateService();
|
||||
var envelope = Encoding.UTF8.GetBytes("{\"payload\":\"test\"}");
|
||||
var digest = "sha256:" + Convert.ToHexString(SHA256.HashData(envelope)).ToLowerInvariant();
|
||||
var tstTime = new DateTimeOffset(2026, 1, 19, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
var attestation = new TimestampedAttestation
|
||||
{
|
||||
Envelope = envelope,
|
||||
EnvelopeDigest = digest,
|
||||
TimeStampToken = Array.Empty<byte>(),
|
||||
TimestampTime = tstTime,
|
||||
TsaName = "Test TSA",
|
||||
TsaPolicyOid = "1.2.3.4",
|
||||
RekorReceipt = new RekorReceipt
|
||||
{
|
||||
LogId = "rekor",
|
||||
LogIndex = 42,
|
||||
IntegratedTime = tstTime.AddMinutes(-10)
|
||||
}
|
||||
};
|
||||
|
||||
var options = new AttestationTimestampVerificationOptions
|
||||
{
|
||||
RequireRekorConsistency = true,
|
||||
MaxTimeSkew = TimeSpan.FromMinutes(5),
|
||||
VerifyTsaRevocation = false
|
||||
};
|
||||
|
||||
var result = await service.VerifyAsync(attestation, options);
|
||||
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.TstStatus.Should().Be(TstVerificationStatus.TimeInconsistency);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Attestor.Timestamping;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.Tests.Timestamping;
|
||||
|
||||
public sealed class TimeCorrelationValidatorTests
|
||||
{
|
||||
private static TimeCorrelationValidator CreateValidator()
|
||||
=> new(NullLogger<TimeCorrelationValidator>.Instance);
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_WhenGapWithinLimits_ReturnsValid()
|
||||
{
|
||||
var validator = CreateValidator();
|
||||
var tstTime = new DateTimeOffset(2026, 1, 19, 12, 0, 0, TimeSpan.Zero);
|
||||
var rekorTime = tstTime.AddSeconds(30);
|
||||
|
||||
var result = validator.Validate(tstTime, rekorTime);
|
||||
|
||||
result.Valid.Should().BeTrue();
|
||||
result.Status.Should().Be(TimeCorrelationStatus.Valid);
|
||||
result.Suspicious.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_WhenGapExceedsMaximum_ReturnsGapExceeded()
|
||||
{
|
||||
var validator = CreateValidator();
|
||||
var policy = new TimeCorrelationPolicy
|
||||
{
|
||||
MaximumGap = TimeSpan.FromMinutes(2),
|
||||
SuspiciousGap = TimeSpan.FromMinutes(1)
|
||||
};
|
||||
var tstTime = new DateTimeOffset(2026, 1, 19, 12, 0, 0, TimeSpan.Zero);
|
||||
var rekorTime = tstTime.AddMinutes(5);
|
||||
|
||||
var result = validator.Validate(tstTime, rekorTime, policy);
|
||||
|
||||
result.Valid.Should().BeFalse();
|
||||
result.Status.Should().Be(TimeCorrelationStatus.GapExceeded);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_WhenTstAfterRekor_ReturnsInvalid()
|
||||
{
|
||||
var validator = CreateValidator();
|
||||
var policy = new TimeCorrelationPolicy
|
||||
{
|
||||
ClockSkewTolerance = TimeSpan.FromSeconds(5)
|
||||
};
|
||||
var tstTime = new DateTimeOffset(2026, 1, 19, 12, 0, 10, TimeSpan.Zero);
|
||||
var rekorTime = tstTime.AddSeconds(-30);
|
||||
|
||||
var result = validator.Validate(tstTime, rekorTime, policy);
|
||||
|
||||
result.Valid.Should().BeFalse();
|
||||
result.Status.Should().Be(TimeCorrelationStatus.TstAfterRekor);
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Validate_WhenSuspiciousAndFailOnSuspicious_ReturnsFailure()
|
||||
{
|
||||
var validator = CreateValidator();
|
||||
var policy = new TimeCorrelationPolicy
|
||||
{
|
||||
MaximumGap = TimeSpan.FromMinutes(5),
|
||||
SuspiciousGap = TimeSpan.FromSeconds(10),
|
||||
FailOnSuspicious = true
|
||||
};
|
||||
var tstTime = new DateTimeOffset(2026, 1, 19, 12, 0, 0, TimeSpan.Zero);
|
||||
var rekorTime = tstTime.AddSeconds(30);
|
||||
|
||||
var result = validator.Validate(tstTime, rekorTime, policy);
|
||||
|
||||
result.Valid.Should().BeFalse();
|
||||
result.Status.Should().Be(TimeCorrelationStatus.SuspiciousGapFailed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user