save progress

This commit is contained in:
StellaOps Bot
2026-01-03 11:02:24 +02:00
parent ca578801fd
commit 83c37243e0
446 changed files with 22798 additions and 4031 deletions

View File

@@ -0,0 +1,175 @@
// Licensed under AGPL-3.0-or-later. Copyright (C) 2024-2026 StellaOps Contributors.
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Attestor.Core.InToto;
using StellaOps.Attestor.Core.Signing;
using StellaOps.Attestor.Core.Submission;
using StellaOps.Attestor.Envelope;
namespace StellaOps.Attestor.Infrastructure.InToto;
/// <summary>
/// Implementation of <see cref="IInTotoLinkSigningService"/> that integrates
/// in-toto link generation with the attestation signing infrastructure.
/// </summary>
internal sealed class InTotoLinkSigningService : IInTotoLinkSigningService
{
private readonly ILinkRecorder _linkRecorder;
private readonly IAttestationSigningService _signingService;
private readonly ILogger<InTotoLinkSigningService> _logger;
private readonly TimeProvider _timeProvider;
public InTotoLinkSigningService(
ILinkRecorder linkRecorder,
IAttestationSigningService signingService,
ILogger<InTotoLinkSigningService> logger,
TimeProvider timeProvider)
{
_linkRecorder = linkRecorder ?? throw new ArgumentNullException(nameof(linkRecorder));
_signingService = signingService ?? throw new ArgumentNullException(nameof(signingService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_timeProvider = timeProvider ?? TimeProvider.System;
}
/// <inheritdoc />
public async Task<SignedInTotoLinkResult> SignLinkAsync(
InTotoLink link,
InTotoLinkSigningOptions options,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(link);
ArgumentNullException.ThrowIfNull(options);
_logger.LogDebug("Signing in-toto link for step {StepName}", link.Predicate.Name);
// Serialize link to JSON payload
var payloadJson = link.ToJson();
var payloadBytes = Encoding.UTF8.GetBytes(payloadJson);
var payloadBase64 = Convert.ToBase64String(payloadBytes);
// Build signing request
var request = new AttestationSignRequest
{
KeyId = options.KeyId ?? string.Empty,
PayloadType = InTotoLink.PredicateTypeUri,
PayloadBase64 = payloadBase64,
Mode = options.Mode,
LogPreference = options.LogPreference,
Archive = options.Archive,
Artifact = new AttestorSubmissionRequest.ArtifactInfo
{
Kind = "in-toto-link",
SubjectUri = $"in-toto:{link.Predicate.Name}"
}
};
// Create submission context for signing
var context = new SubmissionContext
{
CallerSubject = options.CallerSubject ?? "system",
CallerAudience = options.CallerAudience ?? "in-toto-link-signer",
CallerClientId = options.CallerClientId ?? "intoto-link-signing-service",
CallerTenant = options.CallerTenant
};
// Sign the attestation
var signResult = await _signingService.SignAsync(request, context, cancellationToken)
.ConfigureAwait(false);
_logger.LogInformation(
"Signed in-toto link for step {StepName} with key {KeyId}",
link.Predicate.Name,
signResult.KeyId);
// Build DSSE envelope from result
var envelope = BuildEnvelopeFromResult(payloadBytes, signResult);
// Build result
var result = new SignedInTotoLinkResult
{
Link = link,
Envelope = envelope,
SignerKeyId = signResult.KeyId,
Algorithm = signResult.Algorithm,
SignedAt = signResult.SignedAt,
RekorEntry = ExtractRekorEntry(signResult)
};
return result;
}
/// <inheritdoc />
public async Task<SignedInTotoLinkResult> RecordAndSignStepAsync(
string stepName,
Func<Task<int>> action,
IEnumerable<MaterialSpec> materials,
IEnumerable<ProductSpec> products,
InTotoLinkSigningOptions options,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(stepName);
ArgumentNullException.ThrowIfNull(action);
ArgumentNullException.ThrowIfNull(materials);
ArgumentNullException.ThrowIfNull(products);
ArgumentNullException.ThrowIfNull(options);
_logger.LogDebug("Recording and signing step {StepName}", stepName);
// Record the step to create link
var link = await _linkRecorder.RecordStepAsync(
stepName,
action,
materials,
products,
cancellationToken).ConfigureAwait(false);
// Sign the resulting link
return await SignLinkAsync(link, options, cancellationToken).ConfigureAwait(false);
}
private static global::StellaOps.Attestor.Envelope.DsseEnvelope BuildEnvelopeFromResult(
byte[] payloadBytes,
AttestationSignResult signResult)
{
// Extract signature from bundle
var signatures = new List<global::StellaOps.Attestor.Envelope.DsseSignature>();
if (signResult.Bundle.Dsse?.Signatures != null)
{
foreach (var sig in signResult.Bundle.Dsse.Signatures)
{
signatures.Add(new global::StellaOps.Attestor.Envelope.DsseSignature(
sig.Signature,
sig.KeyId));
}
}
if (signatures.Count == 0)
{
// Fallback: create signature from keyId if no envelope signatures present
signatures.Add(new global::StellaOps.Attestor.Envelope.DsseSignature(
"pending", // Will be replaced by actual signing
signResult.KeyId));
}
return new global::StellaOps.Attestor.Envelope.DsseEnvelope(
InTotoLink.PredicateTypeUri,
new ReadOnlyMemory<byte>(payloadBytes),
signatures);
}
// Note: Rekor entry information comes from the submission service after
// the envelope is submitted to the transparency log. The signing service
// produces the signed envelope, but Rekor submission is a separate step.
// For now, we return null and let callers handle Rekor submission separately.
private static RekorEntryReference? ExtractRekorEntry(AttestationSignResult signResult)
{
// The signing result does not include Rekor entry info directly.
// Rekor submission happens in a separate step via IAttestorSubmissionService.
// Callers who need Rekor transparency should submit the result to Rekor
// and capture the entry reference from that operation.
return null;
}
}

View File

@@ -26,6 +26,9 @@ using StellaOps.Attestor.Infrastructure.Transparency;
using StellaOps.Attestor.Infrastructure.Verification;
using StellaOps.Attestor.Infrastructure.Bulk;
using StellaOps.Attestor.Core.Signing;
using StellaOps.Attestor.Core.InToto;
using StellaOps.Attestor.Core.InToto.Layout;
using StellaOps.Attestor.Infrastructure.InToto;
using StellaOps.Attestor.Verify;
namespace StellaOps.Attestor.Infrastructure;
@@ -67,6 +70,12 @@ public static class ServiceCollectionExtensions
services.AddSingleton<IAttestorBundleService, AttestorBundleService>();
services.AddSingleton<AttestorSigningKeyRegistry>();
services.AddSingleton<IAttestationSigningService, AttestorSigningService>();
// In-toto link generation services
services.AddSingleton<ILinkRecorder, LinkRecorder>();
services.AddSingleton<ILayoutVerifier, LayoutVerifier>();
services.AddSingleton<IInTotoLinkSigningService, InTotoLinkSigningService>();
services.AddHttpClient<HttpRekorClient>((sp, client) =>
{
var options = sp.GetRequiredService<IOptions<AttestorOptions>>().Value;