using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using StellaOps.Vexer.Attestation.Models; using StellaOps.Vexer.Attestation.Signing; using StellaOps.Vexer.Core; namespace StellaOps.Vexer.Attestation.Dsse; public sealed class VexDsseBuilder { private const string PayloadType = "application/vnd.in-toto+json"; private readonly IVexSigner _signer; private readonly ILogger _logger; private readonly JsonSerializerOptions _serializerOptions; public VexDsseBuilder(IVexSigner signer, ILogger logger) { _signer = signer ?? throw new ArgumentNullException(nameof(signer)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _serializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.Never, WriteIndented = false, }; _serializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)); } public async ValueTask CreateEnvelopeAsync( VexAttestationRequest request, IReadOnlyDictionary? metadata, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(request); var predicate = VexAttestationPredicate.FromRequest(request, metadata); var subject = new VexInTotoSubject( request.ExportId, new Dictionary(StringComparer.Ordinal) { { request.Artifact.Algorithm.ToLowerInvariant(), request.Artifact.Digest } }); var statement = new VexInTotoStatement( VexInTotoStatement.InTotoType, "https://stella-ops.org/attestations/vex-export", new[] { subject }, predicate); var payloadBytes = JsonSerializer.SerializeToUtf8Bytes(statement, _serializerOptions); var signatureResult = await _signer.SignAsync(payloadBytes, cancellationToken).ConfigureAwait(false); var envelope = new DsseEnvelope( Convert.ToBase64String(payloadBytes), PayloadType, new[] { new DsseSignature(signatureResult.Signature, signatureResult.KeyId) }); _logger.LogDebug("DSSE envelope created for export {ExportId}", request.ExportId); return envelope; } public static string ComputeEnvelopeDigest(DsseEnvelope envelope) { ArgumentNullException.ThrowIfNull(envelope); var envelopeJson = JsonSerializer.Serialize(envelope, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }); var bytes = Encoding.UTF8.GetBytes(envelopeJson); var hash = SHA256.HashData(bytes); return "sha256:" + Convert.ToHexString(hash).ToLowerInvariant(); } }