Refactor code structure for improved readability and maintainability; optimize performance in key functions.

This commit is contained in:
master
2025-12-22 19:06:31 +02:00
parent dfaa2079aa
commit 4602ccc3a3
1444 changed files with 109919 additions and 8058 deletions

View File

@@ -0,0 +1,195 @@
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using StellaOps.DeltaVerdict.Models;
using StellaOps.DeltaVerdict.Serialization;
namespace StellaOps.DeltaVerdict.Signing;
public interface IDeltaSigningService
{
Task<DeltaVerdict.Models.DeltaVerdict> SignAsync(
DeltaVerdict.Models.DeltaVerdict delta,
SigningOptions options,
CancellationToken ct = default);
Task<VerificationResult> VerifyAsync(
DeltaVerdict.Models.DeltaVerdict delta,
VerificationOptions options,
CancellationToken ct = default);
}
public sealed class DeltaSigningService : IDeltaSigningService
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
};
public Task<DeltaVerdict.Models.DeltaVerdict> SignAsync(
DeltaVerdict.Models.DeltaVerdict delta,
SigningOptions options,
CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(delta);
ArgumentNullException.ThrowIfNull(options);
ct.ThrowIfCancellationRequested();
var withDigest = DeltaVerdictSerializer.WithDigest(delta);
var payloadJson = DeltaVerdictSerializer.Serialize(withDigest with { Signature = null });
var payloadBytes = Encoding.UTF8.GetBytes(payloadJson);
var envelope = BuildEnvelope(payloadBytes, options);
var envelopeJson = JsonSerializer.Serialize(envelope, JsonOptions);
return Task.FromResult(withDigest with { Signature = envelopeJson });
}
public Task<VerificationResult> VerifyAsync(
DeltaVerdict.Models.DeltaVerdict delta,
VerificationOptions options,
CancellationToken ct = default)
{
ArgumentNullException.ThrowIfNull(delta);
ArgumentNullException.ThrowIfNull(options);
ct.ThrowIfCancellationRequested();
if (string.IsNullOrEmpty(delta.Signature))
{
return Task.FromResult(VerificationResult.Fail("Delta is not signed"));
}
DsseEnvelope? envelope;
try
{
envelope = JsonSerializer.Deserialize<DsseEnvelope>(delta.Signature, JsonOptions);
}
catch (JsonException ex)
{
return Task.FromResult(VerificationResult.Fail($"Invalid signature envelope: {ex.Message}"));
}
if (envelope is null)
{
return Task.FromResult(VerificationResult.Fail("Signature envelope is empty"));
}
var payloadBytes = Convert.FromBase64String(envelope.Payload);
var pae = BuildPae(envelope.PayloadType, payloadBytes);
var expectedSig = ComputeSignature(pae, options);
var matched = envelope.Signatures.Any(sig =>
string.Equals(sig.KeyId, options.KeyId, StringComparison.Ordinal)
&& string.Equals(sig.Sig, expectedSig, StringComparison.Ordinal));
if (!matched)
{
return Task.FromResult(VerificationResult.Fail("Signature verification failed"));
}
if (!string.IsNullOrEmpty(delta.DeltaDigest))
{
var computed = DeltaVerdictSerializer.ComputeDigest(delta);
if (!string.Equals(computed, delta.DeltaDigest, StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(VerificationResult.Fail("Delta digest mismatch"));
}
}
return Task.FromResult(VerificationResult.Success());
}
private static DsseEnvelope BuildEnvelope(byte[] payload, SigningOptions options)
{
var pae = BuildPae(options.PayloadType, payload);
var signature = ComputeSignature(pae, options);
return new DsseEnvelope(
options.PayloadType,
Convert.ToBase64String(payload),
[new DsseSignature(options.KeyId, signature)]);
}
private static string ComputeSignature(byte[] pae, SigningOptions options)
{
return options.Algorithm switch
{
SigningAlgorithm.HmacSha256 => ComputeHmac(pae, options.SecretBase64),
SigningAlgorithm.Sha256 => Convert.ToBase64String(SHA256.HashData(pae)),
_ => throw new InvalidOperationException($"Unsupported signing algorithm: {options.Algorithm}")
};
}
private static string ComputeHmac(byte[] data, string? secretBase64)
{
if (string.IsNullOrWhiteSpace(secretBase64))
{
throw new InvalidOperationException("HMAC signing requires a base64 secret.");
}
var secret = Convert.FromBase64String(secretBase64);
using var hmac = new HMACSHA256(secret);
var sig = hmac.ComputeHash(data);
return Convert.ToBase64String(sig);
}
private static byte[] BuildPae(string payloadType, byte[] payload)
{
var prefix = "DSSEv1";
var typeBytes = Encoding.UTF8.GetBytes(payloadType);
var prefixBytes = Encoding.UTF8.GetBytes(prefix);
var lengthType = Encoding.UTF8.GetBytes(typeBytes.Length.ToString());
var lengthPayload = Encoding.UTF8.GetBytes(payload.Length.ToString());
using var stream = new MemoryStream();
stream.Write(prefixBytes);
stream.WriteByte((byte)' ');
stream.Write(lengthType);
stream.WriteByte((byte)' ');
stream.Write(typeBytes);
stream.WriteByte((byte)' ');
stream.Write(lengthPayload);
stream.WriteByte((byte)' ');
stream.Write(payload);
return stream.ToArray();
}
}
public sealed record SigningOptions
{
public required string KeyId { get; init; }
public SigningAlgorithm Algorithm { get; init; } = SigningAlgorithm.HmacSha256;
public string? SecretBase64 { get; init; }
public string PayloadType { get; init; } = "application/vnd.stellaops.delta-verdict+json";
}
public sealed record VerificationOptions
{
public required string KeyId { get; init; }
public SigningAlgorithm Algorithm { get; init; } = SigningAlgorithm.HmacSha256;
public string? SecretBase64 { get; init; }
}
public enum SigningAlgorithm
{
HmacSha256,
Sha256
}
public sealed record VerificationResult
{
public required bool IsValid { get; init; }
public string? Error { get; init; }
public static VerificationResult Success() => new() { IsValid = true };
public static VerificationResult Fail(string error) => new() { IsValid = false, Error = error };
}
public sealed record DsseEnvelope(
string PayloadType,
string Payload,
IReadOnlyList<DsseSignature> Signatures);
public sealed record DsseSignature(string KeyId, string Sig);