save work
This commit is contained in:
@@ -36,9 +36,9 @@ public class ProofsApiContractTests : IClassFixture<WebApplicationFactory<Progra
|
||||
var entry = "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abc1:pkg:npm/lodash@4.17.21";
|
||||
var request = new CreateSpineRequest
|
||||
{
|
||||
EvidenceIds = new[] { "sha256:ev123abc456def789012345678901234567890123456789012345678901234" },
|
||||
ReasoningId = "sha256:reason123abc456def789012345678901234567890123456789012345678901",
|
||||
VexVerdictId = "sha256:vex123abc456def789012345678901234567890123456789012345678901234",
|
||||
EvidenceIds = new[] { $"sha256:{new string('a', 64)}" },
|
||||
ReasoningId = $"sha256:{new string('b', 64)}",
|
||||
VexVerdictId = $"sha256:{new string('c', 64)}",
|
||||
PolicyVersion = "v1.0.0"
|
||||
};
|
||||
|
||||
@@ -100,8 +100,8 @@ public class ProofsApiContractTests : IClassFixture<WebApplicationFactory<Progra
|
||||
var request = new CreateSpineRequest
|
||||
{
|
||||
EvidenceIds = new[] { "invalid-not-sha256" }, // Invalid format
|
||||
ReasoningId = "sha256:reason123abc456def789012345678901234567890123456789012345678901",
|
||||
VexVerdictId = "sha256:vex123abc456def789012345678901234567890123456789012345678901234",
|
||||
ReasoningId = $"sha256:{new string('b', 64)}",
|
||||
VexVerdictId = $"sha256:{new string('c', 64)}",
|
||||
PolicyVersion = "v1.0.0"
|
||||
};
|
||||
|
||||
@@ -127,9 +127,9 @@ public class ProofsApiContractTests : IClassFixture<WebApplicationFactory<Progra
|
||||
// Create spine first
|
||||
var createRequest = new CreateSpineRequest
|
||||
{
|
||||
EvidenceIds = new[] { "sha256:ev123abc456def789012345678901234567890123456789012345678901234" },
|
||||
ReasoningId = "sha256:reason123abc456def789012345678901234567890123456789012345678901",
|
||||
VexVerdictId = "sha256:vex123abc456def789012345678901234567890123456789012345678901234",
|
||||
EvidenceIds = new[] { $"sha256:{new string('a', 64)}" },
|
||||
ReasoningId = $"sha256:{new string('b', 64)}",
|
||||
VexVerdictId = $"sha256:{new string('c', 64)}",
|
||||
PolicyVersion = "v1.0.0"
|
||||
};
|
||||
await _client.PostAsJsonAsync($"/proofs/{Uri.EscapeDataString(entry)}/spine", createRequest);
|
||||
@@ -227,9 +227,9 @@ public class ProofsApiContractTests : IClassFixture<WebApplicationFactory<Progra
|
||||
var entry = "sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abc1:pkg:npm/test@1.0.0";
|
||||
var request = new CreateSpineRequest
|
||||
{
|
||||
EvidenceIds = new[] { "sha256:ev123abc456def789012345678901234567890123456789012345678901234" },
|
||||
ReasoningId = "sha256:reason123abc456def789012345678901234567890123456789012345678901",
|
||||
VexVerdictId = "sha256:vex123abc456def789012345678901234567890123456789012345678901234",
|
||||
EvidenceIds = new[] { $"sha256:{new string('a', 64)}" },
|
||||
ReasoningId = $"sha256:{new string('b', 64)}",
|
||||
VexVerdictId = $"sha256:{new string('c', 64)}",
|
||||
PolicyVersion = "v1.0.0"
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
[Collection("SmSoftGate")]
|
||||
public sealed class AttestorSigningServiceTests : IDisposable
|
||||
{
|
||||
private readonly List<string> _temporaryPaths = new();
|
||||
|
||||
@@ -62,6 +62,7 @@ public sealed class AttestorSubmissionServiceTests
|
||||
archiveStore,
|
||||
auditSink,
|
||||
verificationCache,
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
logger,
|
||||
TimeProvider.System,
|
||||
@@ -141,6 +142,7 @@ public sealed class AttestorSubmissionServiceTests
|
||||
archiveStore,
|
||||
auditSink,
|
||||
new StubVerificationCache(),
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
logger,
|
||||
TimeProvider.System,
|
||||
@@ -207,6 +209,7 @@ public sealed class AttestorSubmissionServiceTests
|
||||
archiveStore,
|
||||
auditSink,
|
||||
new StubVerificationCache(),
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
logger,
|
||||
TimeProvider.System,
|
||||
@@ -276,6 +279,7 @@ public sealed class AttestorSubmissionServiceTests
|
||||
archiveStore,
|
||||
auditSink,
|
||||
new StubVerificationCache(),
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
logger,
|
||||
TimeProvider.System,
|
||||
|
||||
@@ -76,6 +76,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
archiveStore,
|
||||
auditSink,
|
||||
new NullVerificationCache(),
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorSubmissionService>(),
|
||||
TimeProvider.System,
|
||||
@@ -98,6 +99,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
rekorClient,
|
||||
new NullTransparencyWitnessClient(),
|
||||
engine,
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorVerificationService>(),
|
||||
metrics,
|
||||
@@ -169,6 +171,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
archiveStore,
|
||||
auditSink,
|
||||
new NullVerificationCache(),
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorSubmissionService>(),
|
||||
TimeProvider.System,
|
||||
@@ -191,6 +194,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
rekorClient,
|
||||
new NullTransparencyWitnessClient(),
|
||||
engine,
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorVerificationService>(),
|
||||
metrics,
|
||||
@@ -253,6 +257,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
archiveStore,
|
||||
auditSink,
|
||||
new NullVerificationCache(),
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorSubmissionService>(),
|
||||
TimeProvider.System,
|
||||
@@ -275,6 +280,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
rekorClient,
|
||||
new NullTransparencyWitnessClient(),
|
||||
engine,
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorVerificationService>(),
|
||||
metrics,
|
||||
@@ -467,6 +473,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
rekorClient,
|
||||
new NullTransparencyWitnessClient(),
|
||||
engine,
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorVerificationService>(),
|
||||
metrics,
|
||||
@@ -552,6 +559,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
rekorClient,
|
||||
new NullTransparencyWitnessClient(),
|
||||
engine,
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorVerificationService>(),
|
||||
metrics,
|
||||
@@ -636,6 +644,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
archiveStore,
|
||||
auditSink,
|
||||
new NullVerificationCache(),
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorSubmissionService>(),
|
||||
TimeProvider.System,
|
||||
@@ -658,6 +667,7 @@ public sealed class AttestorVerificationServiceTests
|
||||
rekorClient,
|
||||
witnessClient,
|
||||
engine,
|
||||
new TimeSkewValidator(options.Value.TimeSkew),
|
||||
options,
|
||||
new NullLogger<AttestorVerificationService>(),
|
||||
metrics,
|
||||
@@ -717,6 +727,15 @@ public sealed class AttestorVerificationServiceTests
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Task<RekorInclusionVerificationResult> VerifyInclusionAsync(
|
||||
string rekorUuid,
|
||||
byte[] payloadDigest,
|
||||
RekorBackend backend,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(RekorInclusionVerificationResult.Failure("not_supported"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ public sealed class CheckpointSignatureVerifierTests
|
||||
private const string ValidCheckpointBody = """
|
||||
rekor.sigstore.dev - 2605736670972794746
|
||||
123456789
|
||||
abc123def456ghi789jkl012mno345pqr678stu901vwx234=
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||
1702345678
|
||||
""";
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Description: PostgreSQL integration tests for Rekor submission queue
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#if STELLAOPS_EXPERIMENTAL_REKOR_QUEUE
|
||||
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -379,6 +381,8 @@ public class PostgresRekorSubmissionQueueIntegrationTests : IAsyncLifetime
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Fake time provider for testing.
|
||||
/// </summary>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
// Task: T11
|
||||
// =============================================================================
|
||||
|
||||
#if STELLAOPS_EXPERIMENTAL_REKOR_QUEUE
|
||||
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -226,3 +228,5 @@ public sealed class RekorSubmissionResponse
|
||||
public string? Uuid { get; init; }
|
||||
public long? Index { get; init; }
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,11 +7,9 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using StellaOps.Attestor.Core.Observability;
|
||||
using StellaOps.Attestor.Core.Options;
|
||||
using StellaOps.Attestor.Core.Queue;
|
||||
using StellaOps.Attestor.Infrastructure.Queue;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
@@ -14,7 +14,8 @@ using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.Tests.Signing;
|
||||
|
||||
public class Sm2AttestorTests
|
||||
[Collection("SmSoftGate")]
|
||||
public sealed class Sm2AttestorTests : IDisposable
|
||||
{
|
||||
private readonly string? _gate;
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.Tests.Signing;
|
||||
|
||||
[CollectionDefinition("SmSoftGate", DisableParallelization = true)]
|
||||
public sealed class SmSoftGateCollection
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
// TimeSkewValidationIntegrationTests.cs
|
||||
// Sprint: SPRINT_3000_0001_0003_rekor_time_skew_validation
|
||||
// Task: T10
|
||||
// Description: Integration tests for time skew validation in submission and verification services
|
||||
// Description: Integration coverage for time skew validation in submission + verification.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Attestor.Core.Observability;
|
||||
@@ -15,575 +13,394 @@ using StellaOps.Attestor.Core.Options;
|
||||
using StellaOps.Attestor.Core.Rekor;
|
||||
using StellaOps.Attestor.Core.Storage;
|
||||
using StellaOps.Attestor.Core.Submission;
|
||||
using StellaOps.Attestor.Core.Transparency;
|
||||
using StellaOps.Attestor.Core.Verification;
|
||||
using StellaOps.Attestor.Infrastructure.Storage;
|
||||
using StellaOps.Attestor.Infrastructure.Submission;
|
||||
using StellaOps.Attestor.Infrastructure.Transparency;
|
||||
using StellaOps.Attestor.Infrastructure.Verification;
|
||||
using StellaOps.Attestor.Tests.Support;
|
||||
using StellaOps.Attestor.Verify;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for time skew validation in submission and verification services.
|
||||
/// Per SPRINT_3000_0001_0003 - T10: Add integration coverage.
|
||||
/// </summary>
|
||||
public sealed class TimeSkewValidationIntegrationTests : IDisposable
|
||||
public sealed class TimeSkewValidationIntegrationTests
|
||||
{
|
||||
private static readonly byte[] HmacSecret = Encoding.UTF8.GetBytes("attestor-hmac-secret");
|
||||
private static readonly string HmacSecretBase64 = Convert.ToBase64String(HmacSecret);
|
||||
|
||||
private readonly AttestorMetrics _metrics;
|
||||
private readonly AttestorActivitySource _activitySource;
|
||||
private readonly DefaultDsseCanonicalizer _canonicalizer;
|
||||
private readonly InMemoryAttestorEntryRepository _repository;
|
||||
private readonly InMemoryAttestorDedupeStore _dedupeStore;
|
||||
private readonly InMemoryAttestorAuditSink _auditSink;
|
||||
private readonly NullAttestorArchiveStore _archiveStore;
|
||||
private readonly NullTransparencyWitnessClient _witnessClient;
|
||||
private readonly NullVerificationCache _verificationCache;
|
||||
private bool _disposed;
|
||||
|
||||
public TimeSkewValidationIntegrationTests()
|
||||
{
|
||||
_metrics = new AttestorMetrics();
|
||||
_activitySource = new AttestorActivitySource();
|
||||
_canonicalizer = new DefaultDsseCanonicalizer();
|
||||
_repository = new InMemoryAttestorEntryRepository();
|
||||
_dedupeStore = new InMemoryAttestorDedupeStore();
|
||||
_auditSink = new InMemoryAttestorAuditSink();
|
||||
_archiveStore = new NullAttestorArchiveStore(new NullLogger<NullAttestorArchiveStore>());
|
||||
_witnessClient = new NullTransparencyWitnessClient();
|
||||
_verificationCache = new NullVerificationCache();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_metrics.Dispose();
|
||||
_activitySource.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#region Submission Integration Tests
|
||||
private static readonly DateTimeOffset FixedNow = new(2025, 12, 18, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
[Fact]
|
||||
public async Task Submission_WithTimeSkewBeyondRejectThreshold_ThrowsTimeSkewValidationException_WhenFailOnRejectEnabled()
|
||||
public async Task SubmitAsync_WhenSkewRejected_Throws_WhenFailOnRejectEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var timeSkewOptions = new TimeSkewOptions
|
||||
var options = CreateOptions(new TimeSkewOptions
|
||||
{
|
||||
Enabled = true,
|
||||
WarnThresholdSeconds = 60,
|
||||
RejectThresholdSeconds = 300,
|
||||
FailOnReject = true
|
||||
};
|
||||
|
||||
var options = CreateAttestorOptions(timeSkewOptions);
|
||||
|
||||
// Create a Rekor client that returns an integrated time way in the past
|
||||
var pastTime = DateTimeOffset.UtcNow.AddSeconds(-600); // 10 minutes ago
|
||||
var rekorClient = new ConfigurableTimeRekorClient(pastTime);
|
||||
|
||||
var timeSkewValidator = new InstrumentedTimeSkewValidator(
|
||||
timeSkewOptions,
|
||||
_metrics,
|
||||
new NullLogger<InstrumentedTimeSkewValidator>());
|
||||
|
||||
var submissionService = CreateSubmissionService(options, rekorClient, timeSkewValidator);
|
||||
var (request, context) = CreateSubmissionRequest();
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<TimeSkewValidationException>(async () =>
|
||||
{
|
||||
await submissionService.SubmitAsync(request, context);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Submission_WithTimeSkewBeyondRejectThreshold_Succeeds_WhenFailOnRejectDisabled()
|
||||
{
|
||||
// Arrange
|
||||
var timeSkewOptions = new TimeSkewOptions
|
||||
{
|
||||
Enabled = true,
|
||||
WarnThresholdSeconds = 60,
|
||||
RejectThresholdSeconds = 300,
|
||||
FailOnReject = false // Disabled - should log but not fail
|
||||
};
|
||||
|
||||
var options = CreateAttestorOptions(timeSkewOptions);
|
||||
|
||||
// Create a Rekor client that returns an integrated time way in the past
|
||||
var pastTime = DateTimeOffset.UtcNow.AddSeconds(-600); // 10 minutes ago
|
||||
var rekorClient = new ConfigurableTimeRekorClient(pastTime);
|
||||
|
||||
var timeSkewValidator = new InstrumentedTimeSkewValidator(
|
||||
timeSkewOptions,
|
||||
_metrics,
|
||||
new NullLogger<InstrumentedTimeSkewValidator>());
|
||||
|
||||
var submissionService = CreateSubmissionService(options, rekorClient, timeSkewValidator);
|
||||
var (request, context) = CreateSubmissionRequest();
|
||||
|
||||
// Act
|
||||
var result = await submissionService.SubmitAsync(request, context);
|
||||
|
||||
// Assert - should succeed but emit metrics
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Uuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Submission_WithTimeSkewBelowWarnThreshold_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var timeSkewOptions = new TimeSkewOptions
|
||||
{
|
||||
Enabled = true,
|
||||
WarnThresholdSeconds = 60,
|
||||
RejectThresholdSeconds = 300,
|
||||
FailOnReject = true
|
||||
};
|
||||
|
||||
var options = CreateAttestorOptions(timeSkewOptions);
|
||||
|
||||
// Create a Rekor client that returns an integrated time just a few seconds ago
|
||||
var recentTime = DateTimeOffset.UtcNow.AddSeconds(-10); // 10 seconds ago
|
||||
var rekorClient = new ConfigurableTimeRekorClient(recentTime);
|
||||
|
||||
var timeSkewValidator = new InstrumentedTimeSkewValidator(
|
||||
timeSkewOptions,
|
||||
_metrics,
|
||||
new NullLogger<InstrumentedTimeSkewValidator>());
|
||||
|
||||
var submissionService = CreateSubmissionService(options, rekorClient, timeSkewValidator);
|
||||
var (request, context) = CreateSubmissionRequest();
|
||||
|
||||
// Act
|
||||
var result = await submissionService.SubmitAsync(request, context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Uuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Submission_WithFutureTimestamp_ThrowsTimeSkewValidationException()
|
||||
{
|
||||
// Arrange
|
||||
var timeSkewOptions = new TimeSkewOptions
|
||||
{
|
||||
Enabled = true,
|
||||
MaxFutureSkewSeconds = 60,
|
||||
FailOnReject = true
|
||||
};
|
||||
|
||||
var options = CreateAttestorOptions(timeSkewOptions);
|
||||
|
||||
// Create a Rekor client that returns a future integrated time
|
||||
var futureTime = DateTimeOffset.UtcNow.AddSeconds(120); // 2 minutes in the future
|
||||
var rekorClient = new ConfigurableTimeRekorClient(futureTime);
|
||||
|
||||
var timeSkewValidator = new InstrumentedTimeSkewValidator(
|
||||
timeSkewOptions,
|
||||
_metrics,
|
||||
new NullLogger<InstrumentedTimeSkewValidator>());
|
||||
|
||||
var submissionService = CreateSubmissionService(options, rekorClient, timeSkewValidator);
|
||||
var (request, context) = CreateSubmissionRequest();
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<TimeSkewValidationException>(async () =>
|
||||
{
|
||||
await submissionService.SubmitAsync(request, context);
|
||||
});
|
||||
|
||||
var canonicalizer = new DefaultDsseCanonicalizer();
|
||||
var validator = new AttestorSubmissionValidator(canonicalizer, options.Value.Security.SignerIdentity.Mode);
|
||||
|
||||
var rekorClient = new FixedRekorClient(integratedTime: FixedNow.AddSeconds(-600));
|
||||
var submissionService = CreateSubmissionService(options, validator, canonicalizer, rekorClient, new TimeSkewValidator(options.Value.TimeSkew), new FixedTimeProvider(FixedNow));
|
||||
|
||||
var request = CreateValidRequest(canonicalizer);
|
||||
var context = CreateSubmissionContext();
|
||||
|
||||
await Assert.ThrowsAsync<TimeSkewValidationException>(() => submissionService.SubmitAsync(request, context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Submission_WhenValidationDisabled_SkipsTimeSkewCheck()
|
||||
public async Task SubmitAsync_WhenSkewRejected_Succeeds_WhenFailOnRejectDisabled()
|
||||
{
|
||||
// Arrange
|
||||
var timeSkewOptions = new TimeSkewOptions
|
||||
{
|
||||
Enabled = false // Disabled
|
||||
};
|
||||
|
||||
var options = CreateAttestorOptions(timeSkewOptions);
|
||||
|
||||
// Create a Rekor client with a very old integrated time
|
||||
var veryOldTime = DateTimeOffset.UtcNow.AddHours(-24);
|
||||
var rekorClient = new ConfigurableTimeRekorClient(veryOldTime);
|
||||
|
||||
var timeSkewValidator = new InstrumentedTimeSkewValidator(
|
||||
timeSkewOptions,
|
||||
_metrics,
|
||||
new NullLogger<InstrumentedTimeSkewValidator>());
|
||||
|
||||
var submissionService = CreateSubmissionService(options, rekorClient, timeSkewValidator);
|
||||
var (request, context) = CreateSubmissionRequest();
|
||||
|
||||
// Act - should succeed even with very old timestamp because validation is disabled
|
||||
var result = await submissionService.SubmitAsync(request, context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Uuid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Verification Integration Tests
|
||||
|
||||
[Fact]
|
||||
public async Task Verification_WithTimeSkewBeyondRejectThreshold_IncludesIssueInReport_WhenFailOnRejectEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var timeSkewOptions = new TimeSkewOptions
|
||||
var options = CreateOptions(new TimeSkewOptions
|
||||
{
|
||||
Enabled = true,
|
||||
WarnThresholdSeconds = 60,
|
||||
RejectThresholdSeconds = 300,
|
||||
FailOnReject = true
|
||||
};
|
||||
MaxFutureSkewSeconds = 60,
|
||||
FailOnReject = false
|
||||
});
|
||||
|
||||
var options = CreateAttestorOptions(timeSkewOptions);
|
||||
var canonicalizer = new DefaultDsseCanonicalizer();
|
||||
var validator = new AttestorSubmissionValidator(canonicalizer, options.Value.Security.SignerIdentity.Mode);
|
||||
|
||||
// First, submit with normal time
|
||||
var submitRekorClient = new ConfigurableTimeRekorClient(DateTimeOffset.UtcNow);
|
||||
var submitTimeSkewValidator = new TimeSkewValidator(new TimeSkewOptions { Enabled = false }); // Disable for submission
|
||||
var rekorClient = new FixedRekorClient(integratedTime: FixedNow.AddSeconds(-600));
|
||||
var submissionService = CreateSubmissionService(options, validator, canonicalizer, rekorClient, new TimeSkewValidator(options.Value.TimeSkew), new FixedTimeProvider(FixedNow));
|
||||
|
||||
var submitService = CreateSubmissionService(options, submitRekorClient, submitTimeSkewValidator);
|
||||
var (request, context) = CreateSubmissionRequest();
|
||||
var submissionResult = await submitService.SubmitAsync(request, context);
|
||||
var request = CreateValidRequest(canonicalizer);
|
||||
var context = CreateSubmissionContext();
|
||||
|
||||
// Now manually update the entry with an old integrated time for verification testing
|
||||
var entry = await _repository.GetByUuidAsync(submissionResult.Uuid);
|
||||
Assert.NotNull(entry);
|
||||
var result = await submissionService.SubmitAsync(request, context);
|
||||
Assert.False(string.IsNullOrWhiteSpace(result.Uuid));
|
||||
}
|
||||
|
||||
// Create a new entry with old integrated time
|
||||
var oldIntegratedTime = DateTimeOffset.UtcNow.AddSeconds(-600); // 10 minutes ago
|
||||
var updatedEntry = entry with
|
||||
[Fact]
|
||||
public async Task VerifyAsync_WhenSkewRejected_ReturnsFailed_WhenFailOnRejectEnabled()
|
||||
{
|
||||
var options = CreateOptions(new TimeSkewOptions
|
||||
{
|
||||
Log = entry.Log with
|
||||
Enabled = true,
|
||||
WarnThresholdSeconds = 60,
|
||||
RejectThresholdSeconds = 300,
|
||||
MaxFutureSkewSeconds = 60,
|
||||
FailOnReject = true
|
||||
});
|
||||
|
||||
var timeProvider = new FixedTimeProvider(FixedNow);
|
||||
var repository = new InMemoryAttestorEntryRepository();
|
||||
|
||||
var entry = new AttestorEntry
|
||||
{
|
||||
RekorUuid = "uuid-1",
|
||||
Artifact = new AttestorEntry.ArtifactDescriptor
|
||||
{
|
||||
IntegratedTimeUtc = oldIntegratedTime
|
||||
Sha256 = new string('a', 64),
|
||||
Kind = "sbom"
|
||||
},
|
||||
BundleSha256 = new string('b', 64),
|
||||
Index = 1,
|
||||
Log = new AttestorEntry.LogDescriptor
|
||||
{
|
||||
Backend = "primary",
|
||||
Url = "https://rekor.example/",
|
||||
IntegratedTime = FixedNow.AddSeconds(-600).ToUnixTimeSeconds()
|
||||
},
|
||||
CreatedAt = FixedNow.AddMinutes(-10),
|
||||
Status = "included",
|
||||
SignerIdentity = new AttestorEntry.SignerIdentityDescriptor
|
||||
{
|
||||
Mode = "keyless",
|
||||
Issuer = "issuer",
|
||||
SubjectAlternativeName = "subject",
|
||||
KeyId = "key-1"
|
||||
}
|
||||
};
|
||||
await _repository.SaveAsync(updatedEntry);
|
||||
|
||||
// Create verification service with time skew validation enabled
|
||||
var verifyTimeSkewValidator = new InstrumentedTimeSkewValidator(
|
||||
timeSkewOptions,
|
||||
_metrics,
|
||||
new NullLogger<InstrumentedTimeSkewValidator>());
|
||||
await repository.SaveAsync(entry);
|
||||
|
||||
var rekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
|
||||
var verificationService = CreateVerificationService(options, rekorClient, verifyTimeSkewValidator);
|
||||
var verificationService = CreateVerificationService(
|
||||
options,
|
||||
canonicalizer: new DefaultDsseCanonicalizer(),
|
||||
repository: repository,
|
||||
timeSkewValidator: new TimeSkewValidator(options.Value.TimeSkew),
|
||||
timeProvider: timeProvider);
|
||||
|
||||
// Act
|
||||
var verifyResult = await verificationService.VerifyAsync(new AttestorVerificationRequest
|
||||
var result = await verificationService.VerifyAsync(new AttestorVerificationRequest
|
||||
{
|
||||
Uuid = submissionResult.Uuid,
|
||||
Bundle = request.Bundle
|
||||
Uuid = entry.RekorUuid,
|
||||
Offline = true,
|
||||
RefreshProof = false
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.False(verifyResult.Ok);
|
||||
Assert.Contains(verifyResult.Issues, i => i.Contains("time_skew"));
|
||||
Assert.False(result.Ok);
|
||||
Assert.Contains(result.Issues, issue => issue.StartsWith("time_skew_rejected:", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Verification_WithTimeSkewBelowThreshold_PassesValidation()
|
||||
public async Task VerifyAsync_WhenSkewRejected_DoesNotFail_WhenFailOnRejectDisabled()
|
||||
{
|
||||
// Arrange
|
||||
var timeSkewOptions = new TimeSkewOptions
|
||||
var options = CreateOptions(new TimeSkewOptions
|
||||
{
|
||||
Enabled = true,
|
||||
WarnThresholdSeconds = 60,
|
||||
RejectThresholdSeconds = 300,
|
||||
FailOnReject = true
|
||||
};
|
||||
|
||||
var options = CreateAttestorOptions(timeSkewOptions);
|
||||
|
||||
// Submit with recent integrated time
|
||||
var recentTime = DateTimeOffset.UtcNow.AddSeconds(-5);
|
||||
var rekorClient = new ConfigurableTimeRekorClient(recentTime);
|
||||
|
||||
var timeSkewValidator = new InstrumentedTimeSkewValidator(
|
||||
timeSkewOptions,
|
||||
_metrics,
|
||||
new NullLogger<InstrumentedTimeSkewValidator>());
|
||||
|
||||
var submitService = CreateSubmissionService(options, rekorClient, timeSkewValidator);
|
||||
var (request, context) = CreateSubmissionRequest();
|
||||
var submissionResult = await submitService.SubmitAsync(request, context);
|
||||
|
||||
// Verify
|
||||
var verifyRekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
|
||||
var verificationService = CreateVerificationService(options, verifyRekorClient, timeSkewValidator);
|
||||
|
||||
// Act
|
||||
var verifyResult = await verificationService.VerifyAsync(new AttestorVerificationRequest
|
||||
{
|
||||
Uuid = submissionResult.Uuid,
|
||||
Bundle = request.Bundle
|
||||
MaxFutureSkewSeconds = 60,
|
||||
FailOnReject = false
|
||||
});
|
||||
|
||||
// Assert - should pass (no time skew issue)
|
||||
// Note: Other issues may exist (e.g., witness_missing) but not time_skew
|
||||
Assert.DoesNotContain(verifyResult.Issues, i => i.Contains("time_skew_rejected"));
|
||||
}
|
||||
var timeProvider = new FixedTimeProvider(FixedNow);
|
||||
var repository = new InMemoryAttestorEntryRepository();
|
||||
|
||||
[Fact]
|
||||
public async Task Verification_OfflineMode_SkipsTimeSkewValidation()
|
||||
{
|
||||
// Arrange
|
||||
var timeSkewOptions = new TimeSkewOptions
|
||||
var entry = new AttestorEntry
|
||||
{
|
||||
Enabled = true, // Enabled, but should be skipped in offline mode due to missing integrated time
|
||||
WarnThresholdSeconds = 60,
|
||||
RejectThresholdSeconds = 300,
|
||||
FailOnReject = true
|
||||
RekorUuid = "uuid-2",
|
||||
Artifact = new AttestorEntry.ArtifactDescriptor
|
||||
{
|
||||
Sha256 = new string('c', 64),
|
||||
Kind = "sbom"
|
||||
},
|
||||
BundleSha256 = new string('d', 64),
|
||||
Index = 1,
|
||||
Log = new AttestorEntry.LogDescriptor
|
||||
{
|
||||
Backend = "primary",
|
||||
Url = "https://rekor.example/",
|
||||
IntegratedTime = FixedNow.AddSeconds(-600).ToUnixTimeSeconds()
|
||||
},
|
||||
CreatedAt = FixedNow.AddMinutes(-10),
|
||||
Status = "included",
|
||||
SignerIdentity = new AttestorEntry.SignerIdentityDescriptor
|
||||
{
|
||||
Mode = "keyless",
|
||||
Issuer = "issuer",
|
||||
SubjectAlternativeName = "subject",
|
||||
KeyId = "key-1"
|
||||
}
|
||||
};
|
||||
|
||||
var options = CreateAttestorOptions(timeSkewOptions);
|
||||
await repository.SaveAsync(entry);
|
||||
|
||||
// Submit without integrated time (simulates offline stored entry)
|
||||
var rekorClient = new ConfigurableTimeRekorClient(integratedTime: null);
|
||||
var timeSkewValidator = new InstrumentedTimeSkewValidator(
|
||||
timeSkewOptions,
|
||||
_metrics,
|
||||
new NullLogger<InstrumentedTimeSkewValidator>());
|
||||
var verificationService = CreateVerificationService(
|
||||
options,
|
||||
canonicalizer: new DefaultDsseCanonicalizer(),
|
||||
repository: repository,
|
||||
timeSkewValidator: new TimeSkewValidator(options.Value.TimeSkew),
|
||||
timeProvider: timeProvider);
|
||||
|
||||
var submitService = CreateSubmissionService(options, rekorClient, timeSkewValidator);
|
||||
var (request, context) = CreateSubmissionRequest();
|
||||
var submissionResult = await submitService.SubmitAsync(request, context);
|
||||
|
||||
// Verify
|
||||
var verifyRekorClient = new StubRekorClient(new NullLogger<StubRekorClient>());
|
||||
var verificationService = CreateVerificationService(options, verifyRekorClient, timeSkewValidator);
|
||||
|
||||
// Act
|
||||
var verifyResult = await verificationService.VerifyAsync(new AttestorVerificationRequest
|
||||
var result = await verificationService.VerifyAsync(new AttestorVerificationRequest
|
||||
{
|
||||
Uuid = submissionResult.Uuid,
|
||||
Bundle = request.Bundle
|
||||
Uuid = entry.RekorUuid,
|
||||
Offline = true,
|
||||
RefreshProof = false
|
||||
});
|
||||
|
||||
// Assert - should not have time skew issues (skipped due to missing integrated time)
|
||||
Assert.DoesNotContain(verifyResult.Issues, i => i.Contains("time_skew_rejected"));
|
||||
Assert.True(result.Ok);
|
||||
Assert.DoesNotContain(result.Issues, issue => issue.StartsWith("time_skew_rejected:", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Metrics Integration Tests
|
||||
|
||||
[Fact]
|
||||
public void TimeSkewMetrics_AreRegistered()
|
||||
{
|
||||
// Assert - metrics should be created
|
||||
Assert.NotNull(_metrics.TimeSkewDetectedTotal);
|
||||
Assert.NotNull(_metrics.TimeSkewSeconds);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private IOptions<AttestorOptions> CreateAttestorOptions(TimeSkewOptions timeSkewOptions)
|
||||
private static IOptions<AttestorOptions> CreateOptions(TimeSkewOptions timeSkew)
|
||||
{
|
||||
return Options.Create(new AttestorOptions
|
||||
{
|
||||
Redis = new AttestorOptions.RedisOptions { Url = string.Empty },
|
||||
Rekor = new AttestorOptions.RekorOptions
|
||||
{
|
||||
Primary = new AttestorOptions.RekorBackendOptions
|
||||
{
|
||||
Url = "https://rekor.stellaops.test",
|
||||
ProofTimeoutMs = 1000,
|
||||
PollIntervalMs = 50,
|
||||
MaxAttempts = 2
|
||||
}
|
||||
},
|
||||
Security = new AttestorOptions.SecurityOptions
|
||||
{
|
||||
SignerIdentity = new AttestorOptions.SignerIdentityOptions
|
||||
Url = "https://rekor.example/"
|
||||
},
|
||||
Mirror = new AttestorOptions.RekorMirrorOptions
|
||||
{
|
||||
Mode = { "kms" },
|
||||
KmsKeys = { HmacSecretBase64 }
|
||||
Enabled = false
|
||||
}
|
||||
},
|
||||
TimeSkew = timeSkewOptions
|
||||
Verification = new AttestorOptions.VerificationOptions
|
||||
{
|
||||
RequireTransparencyInclusion = false,
|
||||
RequireCheckpoint = false,
|
||||
RequireWitnessEndorsement = false
|
||||
},
|
||||
TimeSkew = timeSkew
|
||||
});
|
||||
}
|
||||
|
||||
private AttestorSubmissionService CreateSubmissionService(
|
||||
IOptions<AttestorOptions> options,
|
||||
IRekorClient rekorClient,
|
||||
ITimeSkewValidator timeSkewValidator)
|
||||
private static SubmissionContext CreateSubmissionContext() => new()
|
||||
{
|
||||
return new AttestorSubmissionService(
|
||||
new AttestorSubmissionValidator(_canonicalizer),
|
||||
_repository,
|
||||
_dedupeStore,
|
||||
rekorClient,
|
||||
_witnessClient,
|
||||
_archiveStore,
|
||||
_auditSink,
|
||||
_verificationCache,
|
||||
timeSkewValidator,
|
||||
options,
|
||||
new NullLogger<AttestorSubmissionService>(),
|
||||
TimeProvider.System,
|
||||
_metrics);
|
||||
}
|
||||
CallerSubject = "urn:stellaops:signer",
|
||||
CallerAudience = "attestor",
|
||||
CallerClientId = "signer-service",
|
||||
CallerTenant = "default",
|
||||
ClientCertificate = null,
|
||||
MtlsThumbprint = "00"
|
||||
};
|
||||
|
||||
private AttestorVerificationService CreateVerificationService(
|
||||
IOptions<AttestorOptions> options,
|
||||
IRekorClient rekorClient,
|
||||
ITimeSkewValidator timeSkewValidator)
|
||||
private static AttestorSubmissionRequest CreateValidRequest(DefaultDsseCanonicalizer canonicalizer)
|
||||
{
|
||||
var engine = new AttestorVerificationEngine(
|
||||
_canonicalizer,
|
||||
new TestCryptoHash(),
|
||||
options,
|
||||
new NullLogger<AttestorVerificationEngine>());
|
||||
|
||||
return new AttestorVerificationService(
|
||||
_repository,
|
||||
_canonicalizer,
|
||||
rekorClient,
|
||||
_witnessClient,
|
||||
engine,
|
||||
timeSkewValidator,
|
||||
options,
|
||||
new NullLogger<AttestorVerificationService>(),
|
||||
_metrics,
|
||||
_activitySource,
|
||||
TimeProvider.System);
|
||||
}
|
||||
|
||||
private (AttestorSubmissionRequest Request, SubmissionContext Context) CreateSubmissionRequest()
|
||||
{
|
||||
var artifactSha256 = Convert.ToHexStringLower(RandomNumberGenerator.GetBytes(32));
|
||||
var payloadType = "application/vnd.in-toto+json";
|
||||
var payloadJson = $$$"""{"_type":"https://in-toto.io/Statement/v0.1","subject":[{"name":"test","digest":{"sha256":"{{{artifactSha256}}}"}}],"predicateType":"https://slsa.dev/provenance/v1","predicate":{}}""";
|
||||
var payload = Encoding.UTF8.GetBytes(payloadJson);
|
||||
|
||||
var payloadBase64 = Convert.ToBase64String(payload);
|
||||
|
||||
// Create HMAC signature
|
||||
using var hmac = new HMACSHA256(HmacSecret);
|
||||
var signature = hmac.ComputeHash(payload);
|
||||
var signatureBase64 = Convert.ToBase64String(signature);
|
||||
|
||||
var bundle = new DsseBundle
|
||||
{
|
||||
Mode = "kms",
|
||||
PayloadType = payloadType,
|
||||
Payload = payloadBase64,
|
||||
Signatures =
|
||||
[
|
||||
new DsseSignature
|
||||
{
|
||||
KeyId = "kms-key-1",
|
||||
Sig = signatureBase64
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var bundleBytes = _canonicalizer.Canonicalize(bundle);
|
||||
var bundleSha256 = Convert.ToHexStringLower(SHA256.HashData(bundleBytes));
|
||||
|
||||
var request = new AttestorSubmissionRequest
|
||||
{
|
||||
Bundle = bundle,
|
||||
Meta = new AttestorSubmissionRequest.MetaData
|
||||
Bundle = new AttestorSubmissionRequest.SubmissionBundle
|
||||
{
|
||||
Mode = "keyless",
|
||||
Dsse = new AttestorSubmissionRequest.DsseEnvelope
|
||||
{
|
||||
PayloadType = "application/vnd.in-toto+json",
|
||||
PayloadBase64 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("{}")),
|
||||
Signatures =
|
||||
{
|
||||
new AttestorSubmissionRequest.DsseSignature
|
||||
{
|
||||
KeyId = "test",
|
||||
Signature = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Meta = new AttestorSubmissionRequest.SubmissionMeta
|
||||
{
|
||||
BundleSha256 = bundleSha256,
|
||||
Artifact = new AttestorSubmissionRequest.ArtifactInfo
|
||||
{
|
||||
Sha256 = artifactSha256,
|
||||
Kind = "container",
|
||||
ImageDigest = $"sha256:{artifactSha256}"
|
||||
Sha256 = new string('a', 64),
|
||||
Kind = "sbom"
|
||||
},
|
||||
LogPreference = "primary"
|
||||
LogPreference = "primary",
|
||||
Archive = false
|
||||
}
|
||||
};
|
||||
|
||||
var context = new SubmissionContext
|
||||
{
|
||||
CallerSubject = "urn:stellaops:signer",
|
||||
CallerAudience = "attestor",
|
||||
CallerClientId = "signer-service",
|
||||
CallerTenant = "default"
|
||||
};
|
||||
|
||||
return (request, context);
|
||||
var canonical = canonicalizer.CanonicalizeAsync(request).GetAwaiter().GetResult();
|
||||
request.Meta.BundleSha256 = Convert.ToHexString(SHA256.HashData(canonical)).ToLowerInvariant();
|
||||
return request;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Doubles
|
||||
|
||||
/// <summary>
|
||||
/// A Rekor client that returns configurable integrated times.
|
||||
/// </summary>
|
||||
private sealed class ConfigurableTimeRekorClient : IRekorClient
|
||||
private static AttestorSubmissionService CreateSubmissionService(
|
||||
IOptions<AttestorOptions> options,
|
||||
AttestorSubmissionValidator validator,
|
||||
IDsseCanonicalizer canonicalizer,
|
||||
IRekorClient rekorClient,
|
||||
ITimeSkewValidator timeSkewValidator,
|
||||
TimeProvider timeProvider)
|
||||
{
|
||||
private readonly DateTimeOffset? _integratedTime;
|
||||
private int _callCount;
|
||||
return new AttestorSubmissionService(
|
||||
validator,
|
||||
new InMemoryAttestorEntryRepository(),
|
||||
new InMemoryAttestorDedupeStore(),
|
||||
rekorClient,
|
||||
new NullTransparencyWitnessClient(),
|
||||
new NullAttestorArchiveStore(NullLogger<NullAttestorArchiveStore>.Instance),
|
||||
new InMemoryAttestorAuditSink(),
|
||||
new NullVerificationCache(),
|
||||
timeSkewValidator,
|
||||
options,
|
||||
NullLogger<AttestorSubmissionService>.Instance,
|
||||
timeProvider,
|
||||
new AttestorMetrics());
|
||||
}
|
||||
|
||||
public ConfigurableTimeRekorClient(DateTimeOffset? integratedTime)
|
||||
private static AttestorVerificationService CreateVerificationService(
|
||||
IOptions<AttestorOptions> options,
|
||||
IDsseCanonicalizer canonicalizer,
|
||||
IAttestorEntryRepository repository,
|
||||
ITimeSkewValidator timeSkewValidator,
|
||||
TimeProvider timeProvider)
|
||||
{
|
||||
var engine = new AttestorVerificationEngine(
|
||||
canonicalizer,
|
||||
new TestCryptoHash(),
|
||||
options,
|
||||
NullLogger<AttestorVerificationEngine>.Instance);
|
||||
|
||||
return new AttestorVerificationService(
|
||||
repository,
|
||||
canonicalizer,
|
||||
new NullRekorClient(),
|
||||
new NullTransparencyWitnessClient(),
|
||||
engine,
|
||||
timeSkewValidator,
|
||||
options,
|
||||
NullLogger<AttestorVerificationService>.Instance,
|
||||
new AttestorMetrics(),
|
||||
new AttestorActivitySource(),
|
||||
timeProvider);
|
||||
}
|
||||
|
||||
private sealed class FixedTimeProvider : TimeProvider
|
||||
{
|
||||
private readonly DateTimeOffset _utcNow;
|
||||
|
||||
public FixedTimeProvider(DateTimeOffset utcNow) => _utcNow = utcNow;
|
||||
|
||||
public override DateTimeOffset GetUtcNow() => _utcNow;
|
||||
}
|
||||
|
||||
private sealed class NullVerificationCache : IAttestorVerificationCache
|
||||
{
|
||||
public Task<AttestorVerificationResult?> GetAsync(string subject, string envelopeId, string policyVersion, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<AttestorVerificationResult?>(null);
|
||||
|
||||
public Task SetAsync(string subject, string envelopeId, string policyVersion, AttestorVerificationResult result, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task InvalidateSubjectAsync(string subject, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
|
||||
private sealed class NullRekorClient : IRekorClient
|
||||
{
|
||||
public Task<RekorSubmissionResponse> SubmitAsync(AttestorSubmissionRequest request, RekorBackend backend, CancellationToken cancellationToken = default)
|
||||
=> throw new NotSupportedException("NullRekorClient does not support submissions.");
|
||||
|
||||
public Task<RekorProofResponse?> GetProofAsync(string rekorUuid, RekorBackend backend, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<RekorProofResponse?>(null);
|
||||
|
||||
public Task<RekorInclusionVerificationResult> VerifyInclusionAsync(string rekorUuid, byte[] payloadDigest, RekorBackend backend, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(RekorInclusionVerificationResult.Failure("not_supported"));
|
||||
}
|
||||
|
||||
private sealed class FixedRekorClient : IRekorClient
|
||||
{
|
||||
private readonly long? _integratedTimeSeconds;
|
||||
private readonly RekorProofResponse _proof;
|
||||
|
||||
public FixedRekorClient(DateTimeOffset? integratedTime)
|
||||
{
|
||||
_integratedTime = integratedTime;
|
||||
_integratedTimeSeconds = integratedTime?.ToUnixTimeSeconds();
|
||||
_proof = new RekorProofResponse
|
||||
{
|
||||
Checkpoint = new RekorProofResponse.RekorCheckpoint
|
||||
{
|
||||
Origin = "rekor.test",
|
||||
Size = 1,
|
||||
RootHash = new string('a', 64),
|
||||
Timestamp = FixedNow
|
||||
},
|
||||
Inclusion = new RekorProofResponse.RekorInclusionProof
|
||||
{
|
||||
LeafHash = new string('b', 64),
|
||||
Path = Array.Empty<string>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Task<RekorSubmissionResponse> SubmitAsync(
|
||||
RekorSubmissionRequest request,
|
||||
string url,
|
||||
CancellationToken cancellationToken = default)
|
||||
public Task<RekorSubmissionResponse> SubmitAsync(AttestorSubmissionRequest request, RekorBackend backend, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var uuid = Guid.NewGuid().ToString("N");
|
||||
var index = Interlocked.Increment(ref _callCount);
|
||||
|
||||
return Task.FromResult(new RekorSubmissionResponse
|
||||
{
|
||||
Uuid = uuid,
|
||||
Index = index,
|
||||
LogUrl = url,
|
||||
Index = 1,
|
||||
LogUrl = new Uri(backend.Url, $"/api/v2/log/entries/{uuid}").ToString(),
|
||||
Status = "included",
|
||||
IntegratedTimeUtc = _integratedTime
|
||||
Proof = _proof,
|
||||
IntegratedTime = _integratedTimeSeconds
|
||||
});
|
||||
}
|
||||
|
||||
public Task<RekorProofResponse?> GetProofAsync(
|
||||
string uuid,
|
||||
string url,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult<RekorProofResponse?>(new RekorProofResponse
|
||||
{
|
||||
TreeId = "test-tree-id",
|
||||
LogIndex = 1,
|
||||
TreeSize = 100,
|
||||
RootHash = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)),
|
||||
Hashes = [Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))]
|
||||
});
|
||||
}
|
||||
public Task<RekorProofResponse?> GetProofAsync(string rekorUuid, RekorBackend backend, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<RekorProofResponse?>(_proof);
|
||||
|
||||
public Task<RekorEntryResponse?> GetEntryAsync(
|
||||
string uuid,
|
||||
string url,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult<RekorEntryResponse?>(null);
|
||||
}
|
||||
public Task<RekorInclusionVerificationResult> VerifyInclusionAsync(string rekorUuid, byte[] payloadDigest, RekorBackend backend, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(RekorInclusionVerificationResult.Failure("not_supported"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user