using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace StellaOps.AuditPack.Services; public sealed partial class AuditBundleSigner { /// /// Signs a manifest with DSSE envelope. /// public async Task SignAsync( AuditBundleSigningRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ArgumentNullException.ThrowIfNull(request.ManifestBytes); try { AsymmetricAlgorithm key; string keyId; string algorithm; if (!string.IsNullOrEmpty(request.KeyFilePath)) { (key, keyId, algorithm) = await LoadKeyFromFileAsync( request.KeyFilePath, request.KeyPassword, cancellationToken) .ConfigureAwait(false); } else { var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256); key = ecdsa; keyId = $"ephemeral:{ComputeKeyId(ecdsa)}"; algorithm = "ES256"; } using (key) { var pae = CreatePae(PayloadType, request.ManifestBytes); byte[] signature; if (key is ECDsa ecdsa) { signature = ecdsa.SignData(pae, HashAlgorithmName.SHA256); } else if (key is RSA rsa) { signature = rsa.SignData(pae, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); algorithm = "RS256"; } else { return AuditBundleSigningResult.Failed($"Unsupported key type: {key.GetType().Name}"); } var envelope = new DsseEnvelope { PayloadType = PayloadType, Payload = Convert.ToBase64String(request.ManifestBytes), Signatures = [ new DsseSignature { KeyId = keyId, Sig = Convert.ToBase64String(signature) } ] }; var envelopeBytes = JsonSerializer.SerializeToUtf8Bytes(envelope, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true }); var payloadDigest = ComputeSha256(request.ManifestBytes); return new AuditBundleSigningResult { Success = true, Envelope = envelopeBytes, KeyId = keyId, Algorithm = algorithm, PayloadDigest = payloadDigest }; } } catch (Exception ex) { return AuditBundleSigningResult.Failed($"Signing failed: {ex.Message}"); } } }