98 lines
3.1 KiB
C#
98 lines
3.1 KiB
C#
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
|
|
namespace StellaOps.AuditPack.Services;
|
|
|
|
public sealed partial class AuditBundleSigner
|
|
{
|
|
/// <summary>
|
|
/// Signs a manifest with DSSE envelope.
|
|
/// </summary>
|
|
public async Task<AuditBundleSigningResult> 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}");
|
|
}
|
|
}
|
|
}
|