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:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user