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

@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.ExportCenter.Core.DevPortalOffline;
namespace StellaOps.ExportCenter.Infrastructure.DevPortalOffline;
@@ -13,15 +14,18 @@ namespace StellaOps.ExportCenter.Infrastructure.DevPortalOffline;
public sealed class FileSystemDevPortalOfflineObjectStore : IDevPortalOfflineObjectStore
{
private readonly IOptionsMonitor<DevPortalOfflineStorageOptions> _options;
private readonly ICryptoHash _cryptoHash;
private readonly TimeProvider _timeProvider;
private readonly ILogger<FileSystemDevPortalOfflineObjectStore> _logger;
public FileSystemDevPortalOfflineObjectStore(
IOptionsMonitor<DevPortalOfflineStorageOptions> options,
ICryptoHash cryptoHash,
TimeProvider timeProvider,
ILogger<FileSystemDevPortalOfflineObjectStore> logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
_timeProvider = timeProvider ?? TimeProvider.System;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -40,8 +44,9 @@ public sealed class FileSystemDevPortalOfflineObjectStore : IDevPortalOfflineObj
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
content.Seek(0, SeekOrigin.Begin);
// Write the content to file
using var fileStream = new FileStream(fullPath, FileMode.Create, FileAccess.Write, FileShare.None);
using var hash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256);
var buffer = ArrayPool<byte>.Shared.Rent(128 * 1024);
long totalBytes = 0;
@@ -51,7 +56,6 @@ public sealed class FileSystemDevPortalOfflineObjectStore : IDevPortalOfflineObj
while ((read = await content.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false)) > 0)
{
await fileStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
hash.AppendData(buffer, 0, read);
totalBytes += read;
}
}
@@ -61,9 +65,11 @@ public sealed class FileSystemDevPortalOfflineObjectStore : IDevPortalOfflineObj
}
await fileStream.FlushAsync(cancellationToken).ConfigureAwait(false);
content.Seek(0, SeekOrigin.Begin);
var sha = Convert.ToHexString(hash.GetHashAndReset()).ToLowerInvariant();
// Compute hash from the written file
content.Seek(0, SeekOrigin.Begin);
var sha = await _cryptoHash.ComputeHashHexForPurposeAsync(content, HashPurpose.Content, cancellationToken).ConfigureAwait(false);
content.Seek(0, SeekOrigin.Begin);
var createdAt = _timeProvider.GetUtcNow();
_logger.LogDebug("Stored devportal artefact at {Path} ({Bytes} bytes).", fullPath, totalBytes);

View File

@@ -1,12 +1,12 @@
using System;
using System.Buffers.Binary;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using StellaOps.Cryptography;
using StellaOps.ExportCenter.Core.DevPortalOffline;
namespace StellaOps.ExportCenter.Infrastructure.DevPortalOffline;
@@ -14,15 +14,18 @@ namespace StellaOps.ExportCenter.Infrastructure.DevPortalOffline;
public sealed class HmacDevPortalOfflineManifestSigner : IDevPortalOfflineManifestSigner
{
private readonly IOptionsMonitor<DevPortalOfflineManifestSigningOptions> _options;
private readonly ICryptoHmac _cryptoHmac;
private readonly TimeProvider _timeProvider;
private readonly ILogger<HmacDevPortalOfflineManifestSigner> _logger;
public HmacDevPortalOfflineManifestSigner(
IOptionsMonitor<DevPortalOfflineManifestSigningOptions> options,
ICryptoHmac cryptoHmac,
TimeProvider timeProvider,
ILogger<HmacDevPortalOfflineManifestSigner> logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_cryptoHmac = cryptoHmac ?? throw new ArgumentNullException(nameof(cryptoHmac));
_timeProvider = timeProvider ?? TimeProvider.System;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -49,7 +52,7 @@ public sealed class HmacDevPortalOfflineManifestSigner : IDevPortalOfflineManife
var signedAt = _timeProvider.GetUtcNow();
var payloadBytes = Encoding.UTF8.GetBytes(manifestJson);
var pae = BuildPreAuthEncoding(options.PayloadType, payloadBytes);
var signature = ComputeSignature(options, pae);
var signature = ComputeSignature(options, pae, _cryptoHmac);
var payloadBase64 = Convert.ToBase64String(payloadBytes);
_logger.LogDebug("Signed devportal manifest for bundle {BundleId}.", bundleId);
@@ -82,12 +85,10 @@ public sealed class HmacDevPortalOfflineManifestSigner : IDevPortalOfflineManife
}
}
private static string ComputeSignature(DevPortalOfflineManifestSigningOptions options, byte[] pae)
private static string ComputeSignature(DevPortalOfflineManifestSigningOptions options, byte[] pae, ICryptoHmac cryptoHmac)
{
var secretBytes = Convert.FromBase64String(options.Secret);
using var hmac = new HMACSHA256(secretBytes);
var signatureBytes = hmac.ComputeHash(pae);
return Convert.ToBase64String(signatureBytes);
return cryptoHmac.ComputeHmacBase64ForPurpose(secretBytes, pae, HmacPurpose.Signing);
}
private static byte[] BuildPreAuthEncoding(string payloadType, byte[] payloadBytes)

View File

@@ -11,10 +11,11 @@
<ItemGroup>
<ProjectReference Include="..\StellaOps.ExportCenter.Core\StellaOps.ExportCenter.Core.csproj" />
<ProjectReference Include="..\..\..\TimelineIndexer\StellaOps.TimelineIndexer\StellaOps.TimelineIndexer.Core\StellaOps.TimelineIndexer.Core.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
</ItemGroup>
</Project>