up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
This commit is contained in:
@@ -11,7 +11,8 @@ public enum CryptoCapability
|
||||
Signing,
|
||||
Verification,
|
||||
SymmetricEncryption,
|
||||
KeyDerivation
|
||||
KeyDerivation,
|
||||
ContentHashing
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -30,6 +31,13 @@ public interface ICryptoProvider
|
||||
|
||||
IPasswordHasher GetPasswordHasher(string algorithmId);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a content hasher for the supplied algorithm.
|
||||
/// </summary>
|
||||
/// <param name="algorithmId">Hash algorithm identifier (e.g., SHA-256, GOST-R-34.11-2012-256).</param>
|
||||
/// <returns>Hasher instance.</returns>
|
||||
ICryptoHasher GetHasher(string algorithmId);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a signer for the supplied algorithm and key reference.
|
||||
/// </summary>
|
||||
@@ -81,6 +89,37 @@ public interface ICryptoProviderRegistry
|
||||
string algorithmId,
|
||||
CryptoKeyReference keyReference,
|
||||
string? preferredProvider = null);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a content hasher for the supplied algorithm using registry policy.
|
||||
/// </summary>
|
||||
/// <param name="algorithmId">Hash algorithm identifier (e.g., SHA-256, GOST-R-34.11-2012-256).</param>
|
||||
/// <param name="preferredProvider">Optional provider hint.</param>
|
||||
/// <returns>Resolved hasher with provider name.</returns>
|
||||
CryptoHasherResolution ResolveHasher(string algorithmId, string? preferredProvider = null);
|
||||
}
|
||||
|
||||
public sealed record CryptoSignerResolution(ICryptoSigner Signer, string ProviderName);
|
||||
|
||||
public sealed record CryptoHasherResolution(ICryptoHasher Hasher, string ProviderName);
|
||||
|
||||
/// <summary>
|
||||
/// Content hasher for computing cryptographic digests.
|
||||
/// </summary>
|
||||
public interface ICryptoHasher
|
||||
{
|
||||
/// <summary>
|
||||
/// Algorithm identifier (e.g., SHA-256, GOST-R-34.11-2012-256).
|
||||
/// </summary>
|
||||
string AlgorithmId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Computes the hash of the given data.
|
||||
/// </summary>
|
||||
byte[] ComputeHash(ReadOnlySpan<byte> data);
|
||||
|
||||
/// <summary>
|
||||
/// Computes the hash and returns it as a lowercase hex string.
|
||||
/// </summary>
|
||||
string ComputeHashHex(ReadOnlySpan<byte> data);
|
||||
}
|
||||
|
||||
@@ -109,6 +109,33 @@ public sealed class CryptoProviderRegistry : ICryptoProviderRegistry
|
||||
return new CryptoSignerResolution(resolved, provider.Name);
|
||||
}
|
||||
|
||||
public CryptoHasherResolution ResolveHasher(string algorithmId, string? preferredProvider = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(preferredProvider) &&
|
||||
providersByName.TryGetValue(preferredProvider!, out var hinted))
|
||||
{
|
||||
if (!hinted.Supports(CryptoCapability.ContentHashing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Provider '{preferredProvider}' does not support content hashing with algorithm '{algorithmId}'.");
|
||||
}
|
||||
|
||||
var hasher = hinted.GetHasher(algorithmId);
|
||||
CryptoProviderMetrics.RecordProviderResolution(hinted.Name, CryptoCapability.ContentHashing, algorithmId);
|
||||
return new CryptoHasherResolution(hasher, hinted.Name);
|
||||
}
|
||||
|
||||
var provider = ResolveOrThrow(CryptoCapability.ContentHashing, algorithmId);
|
||||
var resolved = provider.GetHasher(algorithmId);
|
||||
CryptoProviderMetrics.RecordProviderResolution(provider.Name, CryptoCapability.ContentHashing, algorithmId);
|
||||
return new CryptoHasherResolution(resolved, provider.Name);
|
||||
}
|
||||
|
||||
private IEnumerable<ICryptoProvider> EnumerateCandidates()
|
||||
{
|
||||
foreach (var name in preferredOrder)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="ICryptoHasher"/> using BCL cryptographic primitives.
|
||||
/// </summary>
|
||||
public sealed class DefaultCryptoHasher : ICryptoHasher
|
||||
{
|
||||
public DefaultCryptoHasher(string algorithmId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
|
||||
}
|
||||
|
||||
AlgorithmId = algorithmId.ToUpperInvariant();
|
||||
}
|
||||
|
||||
public string AlgorithmId { get; }
|
||||
|
||||
public byte[] ComputeHash(ReadOnlySpan<byte> data)
|
||||
{
|
||||
return AlgorithmId switch
|
||||
{
|
||||
HashAlgorithms.Sha256 => SHA256.HashData(data),
|
||||
HashAlgorithms.Sha384 => SHA384.HashData(data),
|
||||
HashAlgorithms.Sha512 => SHA512.HashData(data),
|
||||
_ => throw new InvalidOperationException($"Unsupported hash algorithm '{AlgorithmId}'.")
|
||||
};
|
||||
}
|
||||
|
||||
public string ComputeHashHex(ReadOnlySpan<byte> data)
|
||||
{
|
||||
var hash = ComputeHash(data);
|
||||
return Convert.ToHexStringLower(hash);
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,8 @@ namespace StellaOps.Cryptography;
|
||||
/// <summary>
|
||||
/// Default in-process crypto provider exposing password hashing capabilities.
|
||||
/// </summary>
|
||||
public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiagnostics
|
||||
{
|
||||
public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiagnostics
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, IPasswordHasher> passwordHashers;
|
||||
private readonly ConcurrentDictionary<string, CryptoSigningKey> signingKeys;
|
||||
private static readonly HashSet<string> SupportedSigningAlgorithms = new(StringComparer.OrdinalIgnoreCase)
|
||||
@@ -18,6 +18,13 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
SignatureAlgorithms.Es256
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> SupportedHashAlgorithms = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
HashAlgorithms.Sha256,
|
||||
HashAlgorithms.Sha384,
|
||||
HashAlgorithms.Sha512
|
||||
};
|
||||
|
||||
public DefaultCryptoProvider()
|
||||
{
|
||||
passwordHashers = new ConcurrentDictionary<string, IPasswordHasher>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -45,6 +52,7 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
{
|
||||
CryptoCapability.PasswordHashing => passwordHashers.ContainsKey(algorithmId),
|
||||
CryptoCapability.Signing or CryptoCapability.Verification => SupportedSigningAlgorithms.Contains(algorithmId),
|
||||
CryptoCapability.ContentHashing => SupportedHashAlgorithms.Contains(algorithmId),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
@@ -59,6 +67,16 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
return passwordHashers[algorithmId];
|
||||
}
|
||||
|
||||
public ICryptoHasher GetHasher(string algorithmId)
|
||||
{
|
||||
if (!Supports(CryptoCapability.ContentHashing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Hash algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
}
|
||||
|
||||
return new DefaultCryptoHasher(algorithmId);
|
||||
}
|
||||
|
||||
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(keyReference);
|
||||
@@ -105,38 +123,38 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
return signingKeys.TryRemove(keyId, out _);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
|
||||
=> signingKeys.Values.ToArray();
|
||||
|
||||
public IEnumerable<CryptoProviderKeyDescriptor> DescribeKeys()
|
||||
{
|
||||
foreach (var key in signingKeys.Values)
|
||||
{
|
||||
var metadata = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["kind"] = key.Kind.ToString(),
|
||||
["createdAt"] = key.CreatedAt.UtcDateTime.ToString("O"),
|
||||
["providerHint"] = key.Reference.ProviderHint,
|
||||
["provider"] = Name
|
||||
};
|
||||
|
||||
if (key.ExpiresAt.HasValue)
|
||||
{
|
||||
metadata["expiresAt"] = key.ExpiresAt.Value.UtcDateTime.ToString("O");
|
||||
}
|
||||
|
||||
foreach (var pair in key.Metadata)
|
||||
{
|
||||
metadata[$"meta.{pair.Key}"] = pair.Value;
|
||||
}
|
||||
|
||||
yield return new CryptoProviderKeyDescriptor(
|
||||
Name,
|
||||
key.Reference.KeyId,
|
||||
key.AlgorithmId,
|
||||
metadata);
|
||||
}
|
||||
}
|
||||
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys()
|
||||
=> signingKeys.Values.ToArray();
|
||||
|
||||
public IEnumerable<CryptoProviderKeyDescriptor> DescribeKeys()
|
||||
{
|
||||
foreach (var key in signingKeys.Values)
|
||||
{
|
||||
var metadata = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["kind"] = key.Kind.ToString(),
|
||||
["createdAt"] = key.CreatedAt.UtcDateTime.ToString("O"),
|
||||
["providerHint"] = key.Reference.ProviderHint,
|
||||
["provider"] = Name
|
||||
};
|
||||
|
||||
if (key.ExpiresAt.HasValue)
|
||||
{
|
||||
metadata["expiresAt"] = key.ExpiresAt.Value.UtcDateTime.ToString("O");
|
||||
}
|
||||
|
||||
foreach (var pair in key.Metadata)
|
||||
{
|
||||
metadata[$"meta.{pair.Key}"] = pair.Value;
|
||||
}
|
||||
|
||||
yield return new CryptoProviderKeyDescriptor(
|
||||
Name,
|
||||
key.Reference.KeyId,
|
||||
key.AlgorithmId,
|
||||
metadata);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureSigningSupported(string algorithmId)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace StellaOps.Cryptography;
|
||||
public static class HashAlgorithms
|
||||
{
|
||||
public const string Sha256 = "SHA256";
|
||||
public const string Sha384 = "SHA384";
|
||||
public const string Sha512 = "SHA512";
|
||||
public const string Gost3411_2012_256 = "GOST3411-2012-256";
|
||||
public const string Gost3411_2012_512 = "GOST3411-2012-512";
|
||||
|
||||
Reference in New Issue
Block a user