Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
mock-dev-release / package-mock-release (push) Has been cancelled
- Added ConsoleExportClient for managing export requests and responses. - Introduced ConsoleExportRequest and ConsoleExportResponse models. - Implemented methods for creating and retrieving exports with appropriate headers. feat(crypto): Add Software SM2/SM3 Cryptography Provider - Implemented SmSoftCryptoProvider for software-only SM2/SM3 cryptography. - Added support for signing and verification using SM2 algorithm. - Included hashing functionality with SM3 algorithm. - Configured options for loading keys from files and environment gate checks. test(crypto): Add unit tests for SmSoftCryptoProvider - Created comprehensive tests for signing, verifying, and hashing functionalities. - Ensured correct behavior for key management and error handling. feat(api): Enhance Console Export Models - Expanded ConsoleExport models to include detailed status and event types. - Added support for various export formats and notification options. test(time): Implement TimeAnchorPolicyService tests - Developed tests for TimeAnchorPolicyService to validate time anchors. - Covered scenarios for anchor validation, drift calculation, and policy enforcement.
151 lines
5.3 KiB
C#
151 lines
5.3 KiB
C#
using StellaOps.AirGap.Time.Models;
|
|
using StellaOps.AirGap.Time.Services;
|
|
|
|
namespace StellaOps.AirGap.Time.Tests;
|
|
|
|
/// <summary>
|
|
/// Tests for RoughtimeVerifier with real Ed25519 signature verification.
|
|
/// Per AIRGAP-TIME-57-001: Trusted time-anchor service.
|
|
/// </summary>
|
|
public class RoughtimeVerifierTests
|
|
{
|
|
private readonly RoughtimeVerifier _verifier = new();
|
|
|
|
[Fact]
|
|
public void Verify_ReturnsFailure_WhenTrustRootsEmpty()
|
|
{
|
|
var token = new byte[] { 0x01, 0x02, 0x03, 0x04 };
|
|
|
|
var result = _verifier.Verify(token, Array.Empty<TimeTrustRoot>(), out var anchor);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Equal("roughtime-trust-roots-required", result.Reason);
|
|
Assert.Equal(TimeAnchor.Unknown, anchor);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ReturnsFailure_WhenTokenEmpty()
|
|
{
|
|
var trust = new[] { new TimeTrustRoot("root1", new byte[32], "ed25519") };
|
|
|
|
var result = _verifier.Verify(ReadOnlySpan<byte>.Empty, trust, out var anchor);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Equal("roughtime-token-empty", result.Reason);
|
|
Assert.Equal(TimeAnchor.Unknown, anchor);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ReturnsFailure_WhenTokenTooShort()
|
|
{
|
|
var token = new byte[] { 0x01, 0x02, 0x03 };
|
|
var trust = new[] { new TimeTrustRoot("root1", new byte[32], "ed25519") };
|
|
|
|
var result = _verifier.Verify(token, trust, out var anchor);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Equal("roughtime-message-too-short", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ReturnsFailure_WhenInvalidTagCount()
|
|
{
|
|
// Create a minimal wire format with invalid tag count
|
|
var token = new byte[8];
|
|
// Set num_tags to 0 (invalid)
|
|
BitConverter.TryWriteBytes(token.AsSpan(0, 4), (uint)0);
|
|
|
|
var trust = new[] { new TimeTrustRoot("root1", new byte[32], "ed25519") };
|
|
|
|
var result = _verifier.Verify(token, trust, out var anchor);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Equal("roughtime-invalid-tag-count", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ReturnsFailure_WhenNonEd25519Algorithm()
|
|
{
|
|
// Create a minimal valid-looking wire format
|
|
var token = CreateMinimalRoughtimeToken();
|
|
var trust = new[] { new TimeTrustRoot("root1", new byte[32], "rsa") }; // Wrong algorithm
|
|
|
|
var result = _verifier.Verify(token, trust, out var anchor);
|
|
|
|
Assert.False(result.IsValid);
|
|
// Should fail either on parsing or signature verification
|
|
Assert.Contains("roughtime-", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ReturnsFailure_WhenKeyLengthWrong()
|
|
{
|
|
var token = CreateMinimalRoughtimeToken();
|
|
var trust = new[] { new TimeTrustRoot("root1", new byte[16], "ed25519") }; // Wrong key length
|
|
|
|
var result = _verifier.Verify(token, trust, out var anchor);
|
|
|
|
Assert.False(result.IsValid);
|
|
Assert.Contains("roughtime-", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public void Verify_ProducesTokenDigest()
|
|
{
|
|
var token = new byte[] { 0xAA, 0xBB, 0xCC, 0xDD };
|
|
var trust = new[] { new TimeTrustRoot("root1", new byte[32], "ed25519") };
|
|
|
|
var result = _verifier.Verify(token, trust, out _);
|
|
|
|
// Even on failure, we should get a deterministic result
|
|
Assert.False(result.IsValid);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a minimal Roughtime wire format token for testing parsing paths.
|
|
/// Note: This will fail signature verification but tests the parsing logic.
|
|
/// </summary>
|
|
private static byte[] CreateMinimalRoughtimeToken()
|
|
{
|
|
// Roughtime wire format:
|
|
// [num_tags:u32] [offsets:u32[n-1]] [tags:u32[n]] [values...]
|
|
// We'll create 2 tags: SIG and SREP
|
|
|
|
const uint TagSig = 0x00474953; // "SIG\0"
|
|
const uint TagSrep = 0x50455253; // "SREP"
|
|
|
|
var sigValue = new byte[64]; // Ed25519 signature
|
|
var srepValue = CreateMinimalSrep();
|
|
|
|
// Header: num_tags=2, offset[0]=64 (sig length), tags=[SIG, SREP]
|
|
var headerSize = 4 + 4 + 8; // num_tags + 1 offset + 2 tags = 16 bytes
|
|
var token = new byte[headerSize + sigValue.Length + srepValue.Length];
|
|
|
|
BitConverter.TryWriteBytes(token.AsSpan(0, 4), (uint)2); // num_tags = 2
|
|
BitConverter.TryWriteBytes(token.AsSpan(4, 4), (uint)64); // offset[0] = 64 (sig length)
|
|
BitConverter.TryWriteBytes(token.AsSpan(8, 4), TagSig);
|
|
BitConverter.TryWriteBytes(token.AsSpan(12, 4), TagSrep);
|
|
sigValue.CopyTo(token.AsSpan(16));
|
|
srepValue.CopyTo(token.AsSpan(16 + 64));
|
|
|
|
return token;
|
|
}
|
|
|
|
private static byte[] CreateMinimalSrep()
|
|
{
|
|
// SREP with MIDP tag containing 8-byte timestamp
|
|
const uint TagMidp = 0x5044494D; // "MIDP"
|
|
|
|
// Header: num_tags=1, tags=[MIDP]
|
|
var headerSize = 4 + 4; // num_tags + 1 tag = 8 bytes
|
|
var srepValue = new byte[headerSize + 8]; // + 8 bytes for MIDP value
|
|
|
|
BitConverter.TryWriteBytes(srepValue.AsSpan(0, 4), (uint)1); // num_tags = 1
|
|
BitConverter.TryWriteBytes(srepValue.AsSpan(4, 4), TagMidp);
|
|
// MIDP value: microseconds since Unix epoch (example: 2025-01-01 00:00:00 UTC)
|
|
BitConverter.TryWriteBytes(srepValue.AsSpan(8, 8), 1735689600000000L);
|
|
|
|
return srepValue;
|
|
}
|
|
}
|