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:
@@ -1,38 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Description>Sender-constrained authentication primitives (DPoP, mTLS) shared across StellaOps services.</Description>
|
||||
<PackageId>StellaOps.Auth.Security</PackageId>
|
||||
<Authors>StellaOps</Authors>
|
||||
<Company>StellaOps</Company>
|
||||
<PackageTags>stellaops;dpop;mtls;oauth2;security</PackageTags>
|
||||
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://stella-ops.org</PackageProjectUrl>
|
||||
<RepositoryUrl>https://git.stella-ops.org/stella-ops.org/git.stella-ops.org</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<VersionPrefix>1.0.0-preview.1</VersionPrefix>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.2.0" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.8.24" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="8.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Description>Sender-constrained authentication primitives (DPoP, mTLS) shared across StellaOps services.</Description>
|
||||
<PackageId>StellaOps.Auth.Security</PackageId>
|
||||
<Authors>StellaOps</Authors>
|
||||
<Company>StellaOps</Company>
|
||||
<PackageTags>stellaops;dpop;mtls;oauth2;security</PackageTags>
|
||||
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://stella-ops.org</PackageProjectUrl>
|
||||
<RepositoryUrl>https://git.stella-ops.org/stella-ops.org/git.stella-ops.org</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<VersionPrefix>1.0.0-preview.1</VersionPrefix>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.2.0" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.8.24" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitLab" Version="8.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.1.0" />
|
||||
<PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -65,6 +65,7 @@ public static class CryptoServiceCollectionExtensions
|
||||
#endif
|
||||
|
||||
services.TryAddSingleton<ICryptoHash, DefaultCryptoHash>();
|
||||
services.TryAddSingleton<ICryptoHmac, DefaultCryptoHmac>();
|
||||
|
||||
services.TryAddSingleton<ICryptoProviderRegistry>(sp =>
|
||||
{
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
<ProjectReference Include="..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.Pkcs11Gost\StellaOps.Cryptography.Plugin.Pkcs11Gost.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Cryptography.Plugin.OpenSslGost\StellaOps.Cryptography.Plugin.OpenSslGost.csproj" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.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.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(StellaOpsEnableCryptoPro)' == 'true'">
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.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.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="AWSSDK.KeyManagementService" Version="4.0.6" />
|
||||
<PackageReference Include="Google.Cloud.Kms.V1" Version="3.19.0" />
|
||||
<PackageReference Include="Pkcs11Interop" Version="4.1.0" />
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<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.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<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>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\\StellaOps.Cryptography\\StellaOps.Cryptography.csproj" />
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<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.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
||||
<PackageReference Include="Pkcs11Interop" Version="4.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -32,6 +32,12 @@ public sealed class ComplianceProfile
|
||||
/// </summary>
|
||||
public required IReadOnlyDictionary<string, string> HashPrefixes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Mapping of HMAC purposes to algorithm identifiers.
|
||||
/// Keys are from <see cref="HmacPurpose"/>, values are from <see cref="HmacAlgorithms"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, string>? HmacPurposeAlgorithms { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// When true, the Interop purpose may use SHA-256 even if not the profile default.
|
||||
/// Default: true.
|
||||
@@ -93,4 +99,27 @@ public sealed class ComplianceProfile
|
||||
|
||||
return string.Equals(expectedAlgorithm, algorithmId, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HMAC algorithm for a given purpose.
|
||||
/// </summary>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <returns>The HMAC algorithm identifier from <see cref="HmacAlgorithms"/>.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the purpose is unknown.</exception>
|
||||
public string GetHmacAlgorithmForPurpose(string purpose)
|
||||
{
|
||||
// WebhookInterop always uses HMAC-SHA256 for external compatibility
|
||||
if (purpose == HmacPurpose.WebhookInterop)
|
||||
{
|
||||
return HmacAlgorithms.HmacSha256;
|
||||
}
|
||||
|
||||
if (HmacPurposeAlgorithms?.TryGetValue(purpose, out var algorithm) == true)
|
||||
{
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
// Default fallback to HMAC-SHA256
|
||||
return HmacAlgorithms.HmacSha256;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,12 @@ public static class ComplianceProfiles
|
||||
[HashPurpose.Interop] = "sha256:",
|
||||
[HashPurpose.Secret] = "argon2id:",
|
||||
},
|
||||
HmacPurposeAlgorithms = new Dictionary<string, string>
|
||||
{
|
||||
[HmacPurpose.Signing] = HmacAlgorithms.HmacSha256,
|
||||
[HmacPurpose.Authentication] = HmacAlgorithms.HmacSha256,
|
||||
[HmacPurpose.WebhookInterop] = HmacAlgorithms.HmacSha256,
|
||||
},
|
||||
AllowInteropOverride = true,
|
||||
};
|
||||
|
||||
@@ -67,6 +73,12 @@ public static class ComplianceProfiles
|
||||
[HashPurpose.Interop] = "sha256:",
|
||||
[HashPurpose.Secret] = "pbkdf2:",
|
||||
},
|
||||
HmacPurposeAlgorithms = new Dictionary<string, string>
|
||||
{
|
||||
[HmacPurpose.Signing] = HmacAlgorithms.HmacSha256,
|
||||
[HmacPurpose.Authentication] = HmacAlgorithms.HmacSha256,
|
||||
[HmacPurpose.WebhookInterop] = HmacAlgorithms.HmacSha256,
|
||||
},
|
||||
AllowInteropOverride = true,
|
||||
};
|
||||
|
||||
@@ -99,6 +111,12 @@ public static class ComplianceProfiles
|
||||
[HashPurpose.Interop] = "sha256:",
|
||||
[HashPurpose.Secret] = "argon2id:",
|
||||
},
|
||||
HmacPurposeAlgorithms = new Dictionary<string, string>
|
||||
{
|
||||
[HmacPurpose.Signing] = HmacAlgorithms.HmacGost3411,
|
||||
[HmacPurpose.Authentication] = HmacAlgorithms.HmacGost3411,
|
||||
[HmacPurpose.WebhookInterop] = HmacAlgorithms.HmacSha256, // External compatibility
|
||||
},
|
||||
AllowInteropOverride = true,
|
||||
};
|
||||
|
||||
@@ -131,6 +149,12 @@ public static class ComplianceProfiles
|
||||
[HashPurpose.Interop] = "sha256:",
|
||||
[HashPurpose.Secret] = "argon2id:",
|
||||
},
|
||||
HmacPurposeAlgorithms = new Dictionary<string, string>
|
||||
{
|
||||
[HmacPurpose.Signing] = HmacAlgorithms.HmacSm3,
|
||||
[HmacPurpose.Authentication] = HmacAlgorithms.HmacSm3,
|
||||
[HmacPurpose.WebhookInterop] = HmacAlgorithms.HmacSha256, // External compatibility
|
||||
},
|
||||
AllowInteropOverride = true,
|
||||
};
|
||||
|
||||
@@ -163,6 +187,12 @@ public static class ComplianceProfiles
|
||||
[HashPurpose.Interop] = "sha256:",
|
||||
[HashPurpose.Secret] = "argon2id:",
|
||||
},
|
||||
HmacPurposeAlgorithms = new Dictionary<string, string>
|
||||
{
|
||||
[HmacPurpose.Signing] = HmacAlgorithms.HmacSha256,
|
||||
[HmacPurpose.Authentication] = HmacAlgorithms.HmacSha256,
|
||||
[HmacPurpose.WebhookInterop] = HmacAlgorithms.HmacSha256,
|
||||
},
|
||||
AllowInteropOverride = true,
|
||||
};
|
||||
|
||||
@@ -195,6 +225,12 @@ public static class ComplianceProfiles
|
||||
[HashPurpose.Interop] = "sha256:",
|
||||
[HashPurpose.Secret] = "argon2id:",
|
||||
},
|
||||
HmacPurposeAlgorithms = new Dictionary<string, string>
|
||||
{
|
||||
[HmacPurpose.Signing] = HmacAlgorithms.HmacSha256,
|
||||
[HmacPurpose.Authentication] = HmacAlgorithms.HmacSha256,
|
||||
[HmacPurpose.WebhookInterop] = HmacAlgorithms.HmacSha256,
|
||||
},
|
||||
AllowInteropOverride = true,
|
||||
};
|
||||
|
||||
|
||||
323
src/__Libraries/StellaOps.Cryptography/DefaultCryptoHmac.cs
Normal file
323
src/__Libraries/StellaOps.Cryptography/DefaultCryptoHmac.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Org.BouncyCastle.Crypto.Digests;
|
||||
using Org.BouncyCastle.Crypto.Macs;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="ICryptoHmac"/> with compliance profile support.
|
||||
/// </summary>
|
||||
public sealed class DefaultCryptoHmac : ICryptoHmac
|
||||
{
|
||||
private readonly IOptionsMonitor<CryptoComplianceOptions> _complianceOptions;
|
||||
private readonly ILogger<DefaultCryptoHmac> _logger;
|
||||
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public DefaultCryptoHmac(
|
||||
IOptionsMonitor<CryptoComplianceOptions>? complianceOptions = null,
|
||||
ILogger<DefaultCryptoHmac>? logger = null)
|
||||
{
|
||||
_complianceOptions = complianceOptions ?? new StaticComplianceOptionsMonitor(new CryptoComplianceOptions());
|
||||
_logger = logger ?? NullLogger<DefaultCryptoHmac>.Instance;
|
||||
}
|
||||
|
||||
internal DefaultCryptoHmac(CryptoComplianceOptions? complianceOptions)
|
||||
: this(
|
||||
new StaticComplianceOptionsMonitor(complianceOptions ?? new CryptoComplianceOptions()),
|
||||
NullLogger<DefaultCryptoHmac>.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DefaultCryptoHmac"/> instance for use in tests.
|
||||
/// Uses default options with no compliance profile.
|
||||
/// </summary>
|
||||
public static DefaultCryptoHmac CreateForTests()
|
||||
=> new(new CryptoComplianceOptions());
|
||||
|
||||
#region Purpose-based methods
|
||||
|
||||
private ComplianceProfile GetActiveProfile()
|
||||
{
|
||||
var opts = _complianceOptions.CurrentValue;
|
||||
opts.ApplyEnvironmentOverrides();
|
||||
return ComplianceProfiles.GetProfile(opts.ProfileId);
|
||||
}
|
||||
|
||||
public byte[] ComputeHmacForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string purpose)
|
||||
{
|
||||
var algorithm = GetAlgorithmForPurpose(purpose);
|
||||
return ComputeHmacWithAlgorithm(key, data, algorithm);
|
||||
}
|
||||
|
||||
public string ComputeHmacHexForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string purpose)
|
||||
=> Convert.ToHexString(ComputeHmacForPurpose(key, data, purpose)).ToLowerInvariant();
|
||||
|
||||
public string ComputeHmacBase64ForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string purpose)
|
||||
=> Convert.ToBase64String(ComputeHmacForPurpose(key, data, purpose));
|
||||
|
||||
public async ValueTask<byte[]> ComputeHmacForPurposeAsync(ReadOnlyMemory<byte> key, Stream stream, string purpose, CancellationToken cancellationToken = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var algorithm = GetAlgorithmForPurpose(purpose);
|
||||
return await ComputeHmacWithAlgorithmAsync(key, stream, algorithm, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async ValueTask<string> ComputeHmacHexForPurposeAsync(ReadOnlyMemory<byte> key, Stream stream, string purpose, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var bytes = await ComputeHmacForPurposeAsync(key, stream, purpose, cancellationToken).ConfigureAwait(false);
|
||||
return Convert.ToHexString(bytes).ToLowerInvariant();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Verification methods
|
||||
|
||||
public bool VerifyHmacForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, ReadOnlySpan<byte> expectedHmac, string purpose)
|
||||
{
|
||||
var computed = ComputeHmacForPurpose(key, data, purpose);
|
||||
return CryptographicOperations.FixedTimeEquals(computed, expectedHmac);
|
||||
}
|
||||
|
||||
public bool VerifyHmacHexForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string expectedHmacHex, string purpose)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(expectedHmacHex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var expectedBytes = Convert.FromHexString(expectedHmacHex);
|
||||
return VerifyHmacForPurpose(key, data, expectedBytes, purpose);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool VerifyHmacBase64ForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string expectedHmacBase64, string purpose)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(expectedHmacBase64))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var expectedBytes = Convert.FromBase64String(expectedHmacBase64);
|
||||
return VerifyHmacForPurpose(key, data, expectedBytes, purpose);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Metadata methods
|
||||
|
||||
public string GetAlgorithmForPurpose(string purpose)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(purpose))
|
||||
{
|
||||
throw new ArgumentException("Purpose cannot be null or empty.", nameof(purpose));
|
||||
}
|
||||
|
||||
var profile = GetActiveProfile();
|
||||
return profile.GetHmacAlgorithmForPurpose(purpose);
|
||||
}
|
||||
|
||||
public int GetOutputLengthForPurpose(string purpose)
|
||||
{
|
||||
var algorithm = GetAlgorithmForPurpose(purpose);
|
||||
return algorithm.ToUpperInvariant() switch
|
||||
{
|
||||
"HMAC-SHA256" => 32,
|
||||
"HMAC-SHA384" => 48,
|
||||
"HMAC-SHA512" => 64,
|
||||
"HMAC-GOST3411" => 32, // GOST R 34.11-2012 Stribog-256
|
||||
"HMAC-SM3" => 32,
|
||||
_ => throw new InvalidOperationException($"Unknown HMAC algorithm '{algorithm}'.")
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Algorithm implementations
|
||||
|
||||
private static byte[] ComputeHmacWithAlgorithm(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string algorithm)
|
||||
{
|
||||
return algorithm.ToUpperInvariant() switch
|
||||
{
|
||||
"HMAC-SHA256" => ComputeHmacSha256(key, data),
|
||||
"HMAC-SHA384" => ComputeHmacSha384(key, data),
|
||||
"HMAC-SHA512" => ComputeHmacSha512(key, data),
|
||||
"HMAC-GOST3411" => ComputeHmacGost3411(key, data),
|
||||
"HMAC-SM3" => ComputeHmacSm3(key, data),
|
||||
_ => throw new InvalidOperationException($"Unsupported HMAC algorithm '{algorithm}'.")
|
||||
};
|
||||
}
|
||||
|
||||
private static async ValueTask<byte[]> ComputeHmacWithAlgorithmAsync(ReadOnlyMemory<byte> key, Stream stream, string algorithm, CancellationToken cancellationToken)
|
||||
{
|
||||
return algorithm.ToUpperInvariant() switch
|
||||
{
|
||||
"HMAC-SHA256" => await ComputeHmacShaStreamAsync(HashAlgorithmName.SHA256, key, stream, cancellationToken).ConfigureAwait(false),
|
||||
"HMAC-SHA384" => await ComputeHmacShaStreamAsync(HashAlgorithmName.SHA384, key, stream, cancellationToken).ConfigureAwait(false),
|
||||
"HMAC-SHA512" => await ComputeHmacShaStreamAsync(HashAlgorithmName.SHA512, key, stream, cancellationToken).ConfigureAwait(false),
|
||||
"HMAC-GOST3411" => await ComputeHmacGost3411StreamAsync(key, stream, cancellationToken).ConfigureAwait(false),
|
||||
"HMAC-SM3" => await ComputeHmacSm3StreamAsync(key, stream, cancellationToken).ConfigureAwait(false),
|
||||
_ => throw new InvalidOperationException($"Unsupported HMAC algorithm '{algorithm}'.")
|
||||
};
|
||||
}
|
||||
|
||||
private static byte[] ComputeHmacSha256(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[32];
|
||||
HMACSHA256.HashData(key, data, buffer);
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] ComputeHmacSha384(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[48];
|
||||
HMACSHA384.HashData(key, data, buffer);
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] ComputeHmacSha512(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[64];
|
||||
HMACSHA512.HashData(key, data, buffer);
|
||||
return buffer.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] ComputeHmacGost3411(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var digest = new Gost3411_2012_256Digest();
|
||||
var hmac = new HMac(digest);
|
||||
hmac.Init(new KeyParameter(key.ToArray()));
|
||||
hmac.BlockUpdate(data.ToArray(), 0, data.Length);
|
||||
var output = new byte[hmac.GetMacSize()];
|
||||
hmac.DoFinal(output, 0);
|
||||
return output;
|
||||
}
|
||||
|
||||
private static byte[] ComputeHmacSm3(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data)
|
||||
{
|
||||
var digest = new SM3Digest();
|
||||
var hmac = new HMac(digest);
|
||||
hmac.Init(new KeyParameter(key.ToArray()));
|
||||
hmac.BlockUpdate(data.ToArray(), 0, data.Length);
|
||||
var output = new byte[hmac.GetMacSize()];
|
||||
hmac.DoFinal(output, 0);
|
||||
return output;
|
||||
}
|
||||
|
||||
private static async ValueTask<byte[]> ComputeHmacShaStreamAsync(HashAlgorithmName name, ReadOnlyMemory<byte> key, Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
using var hmac = name.Name switch
|
||||
{
|
||||
"SHA256" => (HMAC)new HMACSHA256(key.ToArray()),
|
||||
"SHA384" => new HMACSHA384(key.ToArray()),
|
||||
"SHA512" => new HMACSHA512(key.ToArray()),
|
||||
_ => throw new InvalidOperationException($"Unsupported hash algorithm '{name}'.")
|
||||
};
|
||||
|
||||
return await hmac.ComputeHashAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async ValueTask<byte[]> ComputeHmacGost3411StreamAsync(ReadOnlyMemory<byte> key, Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var digest = new Gost3411_2012_256Digest();
|
||||
var hmac = new HMac(digest);
|
||||
hmac.Init(new KeyParameter(key.ToArray()));
|
||||
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(128 * 1024);
|
||||
try
|
||||
{
|
||||
int bytesRead;
|
||||
while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false)) > 0)
|
||||
{
|
||||
hmac.BlockUpdate(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
var output = new byte[hmac.GetMacSize()];
|
||||
hmac.DoFinal(output, 0);
|
||||
return output;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask<byte[]> ComputeHmacSm3StreamAsync(ReadOnlyMemory<byte> key, Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var digest = new SM3Digest();
|
||||
var hmac = new HMac(digest);
|
||||
hmac.Init(new KeyParameter(key.ToArray()));
|
||||
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(128 * 1024);
|
||||
try
|
||||
{
|
||||
int bytesRead;
|
||||
while ((bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false)) > 0)
|
||||
{
|
||||
hmac.BlockUpdate(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
var output = new byte[hmac.GetMacSize()];
|
||||
hmac.DoFinal(output, 0);
|
||||
return output;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static options monitor
|
||||
|
||||
private sealed class StaticComplianceOptionsMonitor : IOptionsMonitor<CryptoComplianceOptions>
|
||||
{
|
||||
private readonly CryptoComplianceOptions _options;
|
||||
|
||||
public StaticComplianceOptionsMonitor(CryptoComplianceOptions options)
|
||||
=> _options = options;
|
||||
|
||||
public CryptoComplianceOptions CurrentValue => _options;
|
||||
|
||||
public CryptoComplianceOptions Get(string? name) => _options;
|
||||
|
||||
public IDisposable OnChange(Action<CryptoComplianceOptions, string> listener)
|
||||
=> NullDisposable.Instance;
|
||||
}
|
||||
|
||||
private sealed class NullDisposable : IDisposable
|
||||
{
|
||||
public static readonly NullDisposable Instance = new();
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
55
src/__Libraries/StellaOps.Cryptography/HmacAlgorithms.cs
Normal file
55
src/__Libraries/StellaOps.Cryptography/HmacAlgorithms.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// Well-known HMAC algorithm identifiers used by compliance profiles.
|
||||
/// </summary>
|
||||
public static class HmacAlgorithms
|
||||
{
|
||||
/// <summary>
|
||||
/// HMAC using SHA-256 (FIPS 198-1, RFC 2104).
|
||||
/// Used by: world, fips, kcmvp, eidas profiles.
|
||||
/// </summary>
|
||||
public const string HmacSha256 = "HMAC-SHA256";
|
||||
|
||||
/// <summary>
|
||||
/// HMAC using SHA-384 (FIPS 198-1, RFC 2104).
|
||||
/// </summary>
|
||||
public const string HmacSha384 = "HMAC-SHA384";
|
||||
|
||||
/// <summary>
|
||||
/// HMAC using SHA-512 (FIPS 198-1, RFC 2104).
|
||||
/// </summary>
|
||||
public const string HmacSha512 = "HMAC-SHA512";
|
||||
|
||||
/// <summary>
|
||||
/// HMAC using GOST R 34.11-2012 Stribog 256-bit (RFC 6986).
|
||||
/// Used by: gost profile.
|
||||
/// </summary>
|
||||
public const string HmacGost3411 = "HMAC-GOST3411";
|
||||
|
||||
/// <summary>
|
||||
/// HMAC using SM3 (GB/T 32905-2016).
|
||||
/// Used by: sm profile.
|
||||
/// </summary>
|
||||
public const string HmacSm3 = "HMAC-SM3";
|
||||
|
||||
/// <summary>
|
||||
/// All known HMAC algorithms for validation.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<string> All = new[]
|
||||
{
|
||||
HmacSha256,
|
||||
HmacSha384,
|
||||
HmacSha512,
|
||||
HmacGost3411,
|
||||
HmacSm3
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Validates whether the given algorithm is a known HMAC algorithm.
|
||||
/// </summary>
|
||||
/// <param name="algorithmId">The algorithm identifier to validate.</param>
|
||||
/// <returns>True if the algorithm is known; otherwise, false.</returns>
|
||||
public static bool IsKnown(string? algorithmId)
|
||||
=> !string.IsNullOrWhiteSpace(algorithmId) && All.Contains(algorithmId);
|
||||
}
|
||||
46
src/__Libraries/StellaOps.Cryptography/HmacPurpose.cs
Normal file
46
src/__Libraries/StellaOps.Cryptography/HmacPurpose.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// Well-known HMAC purpose identifiers for compliance-aware cryptographic operations.
|
||||
/// Components should request HMAC by PURPOSE, not by algorithm.
|
||||
/// The platform resolves the correct algorithm based on the active compliance profile.
|
||||
/// </summary>
|
||||
public static class HmacPurpose
|
||||
{
|
||||
/// <summary>
|
||||
/// DSSE envelope signing and message authentication codes.
|
||||
/// Default: HMAC-SHA256 (world/fips/kcmvp/eidas), HMAC-GOST3411 (gost), HMAC-SM3 (sm).
|
||||
/// </summary>
|
||||
public const string Signing = "signing";
|
||||
|
||||
/// <summary>
|
||||
/// Token and URL authentication (e.g., signed URLs, ack tokens).
|
||||
/// Default: HMAC-SHA256 (world/fips/kcmvp/eidas), HMAC-GOST3411 (gost), HMAC-SM3 (sm).
|
||||
/// </summary>
|
||||
public const string Authentication = "auth";
|
||||
|
||||
/// <summary>
|
||||
/// External webhook interoperability (third-party webhook receivers).
|
||||
/// Always HMAC-SHA256, regardless of compliance profile.
|
||||
/// Every use of this purpose MUST be documented with justification.
|
||||
/// </summary>
|
||||
public const string WebhookInterop = "webhook";
|
||||
|
||||
/// <summary>
|
||||
/// All known HMAC purposes for validation.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyList<string> All = new[]
|
||||
{
|
||||
Signing,
|
||||
Authentication,
|
||||
WebhookInterop
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Validates whether the given purpose is known.
|
||||
/// </summary>
|
||||
/// <param name="purpose">The purpose to validate.</param>
|
||||
/// <returns>True if the purpose is known; otherwise, false.</returns>
|
||||
public static bool IsKnown(string? purpose)
|
||||
=> !string.IsNullOrWhiteSpace(purpose) && All.Contains(purpose);
|
||||
}
|
||||
115
src/__Libraries/StellaOps.Cryptography/ICryptoHmac.cs
Normal file
115
src/__Libraries/StellaOps.Cryptography/ICryptoHmac.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for HMAC (Hash-based Message Authentication Code) operations with compliance profile support.
|
||||
/// </summary>
|
||||
public interface ICryptoHmac
|
||||
{
|
||||
#region Purpose-based methods (preferred for compliance)
|
||||
|
||||
/// <summary>
|
||||
/// Computes an HMAC for the specified purpose using the active compliance profile's algorithm.
|
||||
/// </summary>
|
||||
/// <param name="key">The secret key.</param>
|
||||
/// <param name="data">The data to authenticate.</param>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <returns>The HMAC bytes.</returns>
|
||||
byte[] ComputeHmacForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string purpose);
|
||||
|
||||
/// <summary>
|
||||
/// Computes an HMAC for the specified purpose and returns it as a lowercase hex string.
|
||||
/// </summary>
|
||||
/// <param name="key">The secret key.</param>
|
||||
/// <param name="data">The data to authenticate.</param>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <returns>The HMAC as a lowercase hex string.</returns>
|
||||
string ComputeHmacHexForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string purpose);
|
||||
|
||||
/// <summary>
|
||||
/// Computes an HMAC for the specified purpose and returns it as a Base64 string.
|
||||
/// </summary>
|
||||
/// <param name="key">The secret key.</param>
|
||||
/// <param name="data">The data to authenticate.</param>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <returns>The HMAC as a Base64 string.</returns>
|
||||
string ComputeHmacBase64ForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string purpose);
|
||||
|
||||
/// <summary>
|
||||
/// Computes an HMAC for the specified purpose from a stream asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="key">The secret key.</param>
|
||||
/// <param name="stream">The stream to authenticate.</param>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The HMAC bytes.</returns>
|
||||
ValueTask<byte[]> ComputeHmacForPurposeAsync(ReadOnlyMemory<byte> key, Stream stream, string purpose, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Computes an HMAC for the specified purpose from a stream and returns it as a lowercase hex string.
|
||||
/// </summary>
|
||||
/// <param name="key">The secret key.</param>
|
||||
/// <param name="stream">The stream to authenticate.</param>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>The HMAC as a lowercase hex string.</returns>
|
||||
ValueTask<string> ComputeHmacHexForPurposeAsync(ReadOnlyMemory<byte> key, Stream stream, string purpose, CancellationToken cancellationToken = default);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Verification methods (constant-time comparison)
|
||||
|
||||
/// <summary>
|
||||
/// Verifies an HMAC for the specified purpose using constant-time comparison.
|
||||
/// </summary>
|
||||
/// <param name="key">The secret key.</param>
|
||||
/// <param name="data">The data that was authenticated.</param>
|
||||
/// <param name="expectedHmac">The expected HMAC value.</param>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <returns>True if the HMAC matches; otherwise, false.</returns>
|
||||
bool VerifyHmacForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, ReadOnlySpan<byte> expectedHmac, string purpose);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies an HMAC for the specified purpose using constant-time comparison (hex format).
|
||||
/// </summary>
|
||||
/// <param name="key">The secret key.</param>
|
||||
/// <param name="data">The data that was authenticated.</param>
|
||||
/// <param name="expectedHmacHex">The expected HMAC value as a hex string.</param>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <returns>True if the HMAC matches; otherwise, false.</returns>
|
||||
bool VerifyHmacHexForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string expectedHmacHex, string purpose);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies an HMAC for the specified purpose using constant-time comparison (Base64 format).
|
||||
/// </summary>
|
||||
/// <param name="key">The secret key.</param>
|
||||
/// <param name="data">The data that was authenticated.</param>
|
||||
/// <param name="expectedHmacBase64">The expected HMAC value as a Base64 string.</param>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <returns>True if the HMAC matches; otherwise, false.</returns>
|
||||
bool VerifyHmacBase64ForPurpose(ReadOnlySpan<byte> key, ReadOnlySpan<byte> data, string expectedHmacBase64, string purpose);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Metadata methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the algorithm that will be used for the specified purpose based on the active compliance profile.
|
||||
/// </summary>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <returns>The algorithm identifier (e.g., "HMAC-SHA256", "HMAC-GOST3411").</returns>
|
||||
string GetAlgorithmForPurpose(string purpose);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the expected HMAC output length in bytes for the specified purpose.
|
||||
/// </summary>
|
||||
/// <param name="purpose">The HMAC purpose from <see cref="HmacPurpose"/>.</param>
|
||||
/// <returns>The output length in bytes.</returns>
|
||||
int GetOutputLengthForPurpose(string purpose);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(StellaOpsCryptoSodium)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);STELLAOPS_CRYPTO_SODIUM</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blake3" Version="1.1.0" />
|
||||
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(StellaOpsCryptoSodium)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);STELLAOPS_CRYPTO_SODIUM</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blake3" Version="1.1.0" />
|
||||
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.5.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
</ItemGroup>
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -13,13 +13,13 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<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.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<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.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.7.1" />
|
||||
<PackageReference Include="JsonSchema.Net" Version="5.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<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.Options.ConfigurationExtensions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.0" />
|
||||
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="2.1.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<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.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<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.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<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.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<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.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<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" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.Tcp\StellaOps.Router.Transport.Tcp.csproj" />
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.Tls\StellaOps.Router.Transport.Tls.csproj" />
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Router.Transport.Udp\StellaOps.Router.Transport.Udp.csproj" />
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0-rc.2.25502.107" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="Mongo2Go" Version="4.1.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.1" />
|
||||
@@ -22,8 +22,8 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../Signals/StellaOps.Signals/StellaOps.Signals.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../../Signals/StellaOps.Signals/StellaOps.Signals.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user