Rename Vexer to Excititor
This commit is contained in:
108
src/StellaOps.Excititor.Attestation/VexAttestationClient.cs
Normal file
108
src/StellaOps.Excititor.Attestation/VexAttestationClient.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Excititor.Attestation.Dsse;
|
||||
using StellaOps.Excititor.Attestation.Models;
|
||||
using StellaOps.Excititor.Attestation.Signing;
|
||||
using StellaOps.Excititor.Attestation.Transparency;
|
||||
using StellaOps.Excititor.Core;
|
||||
|
||||
namespace StellaOps.Excititor.Attestation;
|
||||
|
||||
public sealed class VexAttestationClientOptions
|
||||
{
|
||||
public IReadOnlyDictionary<string, string> DefaultMetadata { get; set; } = ImmutableDictionary<string, string>.Empty;
|
||||
}
|
||||
|
||||
public sealed class VexAttestationClient : IVexAttestationClient
|
||||
{
|
||||
private readonly VexDsseBuilder _builder;
|
||||
private readonly ILogger<VexAttestationClient> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IReadOnlyDictionary<string, string> _defaultMetadata;
|
||||
private readonly ITransparencyLogClient? _transparencyLogClient;
|
||||
|
||||
public VexAttestationClient(
|
||||
VexDsseBuilder builder,
|
||||
IOptions<VexAttestationClientOptions> options,
|
||||
ILogger<VexAttestationClient> logger,
|
||||
TimeProvider? timeProvider = null,
|
||||
ITransparencyLogClient? transparencyLogClient = null)
|
||||
{
|
||||
_builder = builder ?? throw new ArgumentNullException(nameof(builder));
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_defaultMetadata = options.Value.DefaultMetadata;
|
||||
_transparencyLogClient = transparencyLogClient;
|
||||
}
|
||||
|
||||
public async ValueTask<VexAttestationResponse> SignAsync(VexAttestationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
var mergedMetadata = MergeMetadata(request.Metadata, _defaultMetadata);
|
||||
|
||||
var envelope = await _builder.CreateEnvelopeAsync(request, mergedMetadata, cancellationToken).ConfigureAwait(false);
|
||||
var envelopeDigest = VexDsseBuilder.ComputeEnvelopeDigest(envelope);
|
||||
var signedAt = _timeProvider.GetUtcNow();
|
||||
|
||||
var diagnosticsBuilder = ImmutableDictionary<string, string>.Empty
|
||||
.Add("envelope", JsonSerializer.Serialize(envelope))
|
||||
.Add("predicateType", "https://stella-ops.org/attestations/vex-export");
|
||||
|
||||
VexRekorReference? rekorReference = null;
|
||||
if (_transparencyLogClient is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var entry = await _transparencyLogClient.SubmitAsync(envelope, cancellationToken).ConfigureAwait(false);
|
||||
rekorReference = new VexRekorReference("0.2", entry.Location, entry.LogIndex, entry.InclusionProofUrl is not null ? new Uri(entry.InclusionProofUrl) : null);
|
||||
diagnosticsBuilder = diagnosticsBuilder.Add("rekorLocation", entry.Location);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to submit attestation to Rekor transparency log");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
var metadata = new VexAttestationMetadata(
|
||||
predicateType: "https://stella-ops.org/attestations/vex-export",
|
||||
rekor: rekorReference,
|
||||
envelopeDigest: envelopeDigest,
|
||||
signedAt: signedAt);
|
||||
|
||||
_logger.LogInformation("Generated DSSE envelope for export {ExportId} ({Digest})", request.ExportId, envelopeDigest);
|
||||
|
||||
return new VexAttestationResponse(metadata, diagnosticsBuilder);
|
||||
}
|
||||
|
||||
public ValueTask<VexAttestationVerification> VerifyAsync(VexAttestationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
// Placeholder until verification flow is implemented in EXCITITOR-ATTEST-01-003.
|
||||
return ValueTask.FromResult(new VexAttestationVerification(true, ImmutableDictionary<string, string>.Empty));
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, string> MergeMetadata(
|
||||
IReadOnlyDictionary<string, string> requestMetadata,
|
||||
IReadOnlyDictionary<string, string> defaults)
|
||||
{
|
||||
if (defaults.Count == 0)
|
||||
{
|
||||
return requestMetadata;
|
||||
}
|
||||
|
||||
var merged = new Dictionary<string, string>(defaults, StringComparer.Ordinal);
|
||||
foreach (var kvp in requestMetadata)
|
||||
{
|
||||
merged[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
return merged.ToImmutableDictionary(StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user