feat: Implement DefaultCryptoHmac for compliance-aware HMAC operations

- Added DefaultCryptoHmac class implementing ICryptoHmac interface.
- Introduced purpose-based HMAC computation methods.
- Implemented verification methods for HMACs with constant-time comparison.
- Created HmacAlgorithms and HmacPurpose classes for well-known identifiers.
- Added compliance profile support for HMAC algorithms.
- Included asynchronous methods for HMAC computation from streams.
This commit is contained in:
StellaOps Bot
2025-12-06 00:41:04 +02:00
parent 43c281a8b2
commit f0662dd45f
362 changed files with 8441 additions and 22338 deletions

View File

@@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.Scanner.Surface.Env;
using StellaOps.Scanner.Surface.Secrets;
using StellaOps.Scanner.Worker.Options;
@@ -18,20 +19,23 @@ namespace StellaOps.Scanner.Worker.Processing.Surface;
/// DSSE envelope signer that prefers an HMAC key (deterministic) and falls back to
/// the deterministic hash-only signer when no key is configured.
/// </summary>
internal sealed class HmacDsseEnvelopeSigner : IDsseEnvelopeSigner, IDisposable
internal sealed class HmacDsseEnvelopeSigner : IDsseEnvelopeSigner
{
private readonly ILogger<HmacDsseEnvelopeSigner> _logger;
private readonly ScannerWorkerOptions _options;
private readonly ICryptoHmac _cryptoHmac;
private readonly DeterministicDsseEnvelopeSigner _deterministic = new();
private readonly HMACSHA256? _hmac;
private readonly byte[]? _secretBytes;
private readonly string _keyId;
public HmacDsseEnvelopeSigner(
IOptions<ScannerWorkerOptions> options,
ICryptoHmac cryptoHmac,
ILogger<HmacDsseEnvelopeSigner> logger,
IServiceProvider serviceProvider)
{
ArgumentNullException.ThrowIfNull(options);
_cryptoHmac = cryptoHmac ?? throw new ArgumentNullException(nameof(cryptoHmac));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options.Value ?? throw new ArgumentNullException(nameof(options));
@@ -52,8 +56,8 @@ internal sealed class HmacDsseEnvelopeSigner : IDsseEnvelopeSigner, IDisposable
if (secretBytes is not null && secretBytes.Length > 0)
{
_hmac = new HMACSHA256(secretBytes);
_logger.LogInformation("DSSE signing enabled using HMAC-SHA256 with key id {KeyId}", _keyId);
_secretBytes = secretBytes;
_logger.LogInformation("DSSE signing enabled using HMAC with key id {KeyId}", _keyId);
}
else if (!signing.AllowDeterministicFallback)
{
@@ -67,13 +71,13 @@ internal sealed class HmacDsseEnvelopeSigner : IDsseEnvelopeSigner, IDisposable
public Task<DsseEnvelope> SignAsync(string payloadType, ReadOnlyMemory<byte> content, string suggestedKind, string merkleRoot, string? view, CancellationToken cancellationToken)
{
if (_hmac is null)
if (_secretBytes is null)
{
return _deterministic.SignAsync(payloadType, content, suggestedKind, merkleRoot, view, cancellationToken);
}
var pae = BuildPae(payloadType, content.Span);
var signatureBytes = _hmac.ComputeHash(pae);
var signatureBytes = _cryptoHmac.ComputeHmacForPurpose(_secretBytes, pae, HmacPurpose.Signing);
var envelope = new
{
payloadType,
@@ -96,11 +100,6 @@ internal sealed class HmacDsseEnvelopeSigner : IDsseEnvelopeSigner, IDisposable
return Task.FromResult(new DsseEnvelope("application/vnd.dsse+json", uri, digest, bytes));
}
public void Dispose()
{
_hmac?.Dispose();
}
private static byte[]? LoadSecret(ScannerWorkerOptions.SigningOptions signing)
{
if (!string.IsNullOrWhiteSpace(signing.SharedSecretFile) && File.Exists(signing.SharedSecretFile))