search and ai stabilization work, localization stablized.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -21,7 +22,7 @@ public sealed partial class Argon2idPasswordHasher : IPasswordHasher
|
||||
|
||||
if (options.Algorithm != PasswordHashAlgorithm.Argon2id)
|
||||
{
|
||||
throw new InvalidOperationException("Argon2idPasswordHasher only supports the Argon2id algorithm.");
|
||||
throw new InvalidOperationException(_t("crypto.password.algorithm_mismatch", "Argon2idPasswordHasher", "Argon2id"));
|
||||
}
|
||||
|
||||
Span<byte> salt = stackalloc byte[SaltLengthBytes];
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -32,12 +33,12 @@ public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnos
|
||||
|
||||
if (this.signingAlgorithms.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one signing algorithm must be supplied.", nameof(signingAlgorithms));
|
||||
throw new ArgumentException(_t("crypto.compliance.at_least_one_signing"), nameof(signingAlgorithms));
|
||||
}
|
||||
|
||||
if (this.hashAlgorithms.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one hash algorithm must be supplied.", nameof(hashAlgorithms));
|
||||
throw new ArgumentException(_t("crypto.compliance.at_least_one_hash"), nameof(hashAlgorithms));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnos
|
||||
}
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
||||
=> throw new NotSupportedException($"Provider '{Name}' does not expose password hashing.");
|
||||
=> throw new NotSupportedException(_t("crypto.provider.no_password_hashing", Name));
|
||||
|
||||
public ICryptoHasher GetHasher(string algorithmId)
|
||||
{
|
||||
@@ -74,12 +75,12 @@ public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnos
|
||||
|
||||
if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey))
|
||||
{
|
||||
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
|
||||
throw new KeyNotFoundException(_t("crypto.provider.key_not_registered", keyReference.KeyId, Name));
|
||||
}
|
||||
|
||||
if (!string.Equals(signingKey.AlgorithmId, NormalizeAlg(algorithmId), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.key_algorithm_mismatch", keyReference.KeyId, signingKey.AlgorithmId, algorithmId));
|
||||
}
|
||||
|
||||
return EcdsaSigner.Create(signingKey);
|
||||
@@ -89,7 +90,7 @@ public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnos
|
||||
{
|
||||
if (!Supports(CryptoCapability.Verification, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Verification algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.verify_not_supported", algorithmId, Name));
|
||||
}
|
||||
|
||||
return EcdsaSigner.CreateVerifierFromPublicKey(algorithmId, publicKeyBytes);
|
||||
@@ -102,7 +103,7 @@ public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnos
|
||||
|
||||
if (signingKey.Kind != CryptoSigningKeyKind.Ec)
|
||||
{
|
||||
throw new InvalidOperationException($"Provider '{Name}' only accepts EC signing keys.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.ec_keys_only", Name));
|
||||
}
|
||||
|
||||
ValidateCurve(signingKey.AlgorithmId, signingKey.PrivateParameters);
|
||||
@@ -155,7 +156,7 @@ public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnos
|
||||
{
|
||||
if (!Supports(CryptoCapability.Signing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.algorithm_not_supported", algorithmId, Name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,7 +164,7 @@ public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnos
|
||||
{
|
||||
if (!Supports(CryptoCapability.ContentHashing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Hash algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.hash_not_supported", algorithmId, Name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +187,7 @@ public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnos
|
||||
|
||||
if (!matches)
|
||||
{
|
||||
throw new InvalidOperationException($"Signing key curve mismatch. Expected curve '{expectedCurve}' for algorithm '{algorithmId}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.curve_mismatch", expectedCurve, algorithmId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +197,7 @@ public class EcdsaPolicyCryptoProvider : ICryptoProvider, ICryptoProviderDiagnos
|
||||
SignatureAlgorithms.Es256 => JsonWebKeyECTypes.P256,
|
||||
SignatureAlgorithms.Es384 => JsonWebKeyECTypes.P384,
|
||||
SignatureAlgorithms.Es512 => JsonWebKeyECTypes.P521,
|
||||
_ => throw new InvalidOperationException($"Unsupported ECDSA curve mapping for algorithm '{algorithmId}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.ecdsa.curve_unsupported", algorithmId))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -251,13 +252,13 @@ public sealed class KcmvpHashOnlyProvider : ICryptoProvider
|
||||
}
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
||||
=> throw new NotSupportedException("KCMVP hash provider does not expose password hashing.");
|
||||
=> throw new NotSupportedException(_t("crypto.provider.no_password_hashing", Name));
|
||||
|
||||
public ICryptoHasher GetHasher(string algorithmId)
|
||||
{
|
||||
if (!Supports(CryptoCapability.ContentHashing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Hash algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.hash_not_supported", algorithmId, Name));
|
||||
}
|
||||
|
||||
return new DefaultCryptoHasher(HashAlgorithms.Sha256);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
/// <summary>
|
||||
@@ -57,7 +59,7 @@ public sealed class ComplianceProfile
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Unknown hash purpose '{purpose}' in profile '{ProfileId}'.", nameof(purpose));
|
||||
throw new ArgumentException(_t("crypto.profile.unknown_purpose", purpose, ProfileId), nameof(purpose));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -54,7 +55,7 @@ public interface ICryptoProvider
|
||||
/// <param name="publicKeyBytes">Public key in SubjectPublicKeyInfo format (DER-encoded).</param>
|
||||
/// <returns>Ephemeral signer instance (supports VerifyAsync only).</returns>
|
||||
ICryptoSigner CreateEphemeralVerifier(string algorithmId, ReadOnlySpan<byte> publicKeyBytes)
|
||||
=> throw new NotSupportedException($"Provider '{Name}' does not support ephemeral verification.");
|
||||
=> throw new NotSupportedException(_t("crypto.provider.no_ephemeral_verification", Name));
|
||||
|
||||
/// <summary>
|
||||
/// Adds or replaces signing key material managed by this provider.
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -29,7 +30,7 @@ public sealed class CryptoProviderRegistry : ICryptoProviderRegistry
|
||||
var providerList = providers.ToList();
|
||||
if (providerList.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one crypto provider must be registered.", nameof(providers));
|
||||
throw new ArgumentException(_t("crypto.registry.empty"), nameof(providers));
|
||||
}
|
||||
|
||||
providersByName = providerList.ToDictionary(p => p.Name, StringComparer.OrdinalIgnoreCase);
|
||||
@@ -66,7 +67,7 @@ public sealed class CryptoProviderRegistry : ICryptoProviderRegistry
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
|
||||
throw new ArgumentException(_t("crypto.registry.algorithm_required"), nameof(algorithmId));
|
||||
}
|
||||
|
||||
foreach (var provider in EnumerateCandidates())
|
||||
@@ -79,8 +80,14 @@ public sealed class CryptoProviderRegistry : ICryptoProviderRegistry
|
||||
}
|
||||
|
||||
CryptoProviderMetrics.RecordProviderResolutionFailure(capability, algorithmId);
|
||||
throw new InvalidOperationException(
|
||||
$"No crypto provider is registered for capability '{capability}' and algorithm '{algorithmId}'.");
|
||||
var notSupportedMessage = capability switch
|
||||
{
|
||||
CryptoCapability.Signing => _t("crypto.registry.signing_not_supported", algorithmId),
|
||||
CryptoCapability.ContentHashing => _t("crypto.registry.hash_not_supported", algorithmId),
|
||||
CryptoCapability.Verification => _t("crypto.registry.verify_not_supported", algorithmId),
|
||||
_ => _t("crypto.registry.signing_not_supported", algorithmId)
|
||||
};
|
||||
throw new InvalidOperationException(notSupportedMessage);
|
||||
}
|
||||
|
||||
public CryptoSignerResolution ResolveSigner(
|
||||
@@ -113,7 +120,7 @@ public sealed class CryptoProviderRegistry : ICryptoProviderRegistry
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
|
||||
throw new ArgumentException(_t("crypto.registry.algorithm_required"), nameof(algorithmId));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(preferredProvider) &&
|
||||
@@ -122,7 +129,7 @@ public sealed class CryptoProviderRegistry : ICryptoProviderRegistry
|
||||
if (!hinted.Supports(CryptoCapability.ContentHashing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Provider '{preferredProvider}' does not support content hashing with algorithm '{algorithmId}'.");
|
||||
_t("crypto.provider.no_content_hashing", preferredProvider));
|
||||
}
|
||||
|
||||
var hasher = hinted.GetHasher(algorithmId);
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -39,12 +40,12 @@ public sealed class CryptoSigningKey
|
||||
|
||||
if (string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
|
||||
throw new ArgumentException(_t("crypto.key.algorithm_required"), nameof(algorithmId));
|
||||
}
|
||||
|
||||
if (privateParameters.D is null || privateParameters.D.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Private key parameters must include the scalar component.", nameof(privateParameters));
|
||||
throw new ArgumentException(_t("crypto.key.private_scalar_required"), nameof(privateParameters));
|
||||
}
|
||||
|
||||
AlgorithmId = algorithmId;
|
||||
@@ -79,19 +80,19 @@ public sealed class CryptoSigningKey
|
||||
{
|
||||
if (!verificationOnly)
|
||||
{
|
||||
throw new ArgumentException("This constructor is only for verification-only keys. Set verificationOnly to true.", nameof(verificationOnly));
|
||||
throw new ArgumentException(_t("crypto.key.verification_only"), nameof(verificationOnly));
|
||||
}
|
||||
|
||||
Reference = reference ?? throw new ArgumentNullException(nameof(reference));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
|
||||
throw new ArgumentException(_t("crypto.key.algorithm_required"), nameof(algorithmId));
|
||||
}
|
||||
|
||||
if (publicParameters.Q.X is null || publicParameters.Q.Y is null)
|
||||
{
|
||||
throw new ArgumentException("Public key parameters must include X and Y coordinates.", nameof(publicParameters));
|
||||
throw new ArgumentException(_t("crypto.key.public_xy_required"), nameof(publicParameters));
|
||||
}
|
||||
|
||||
AlgorithmId = algorithmId;
|
||||
@@ -125,12 +126,12 @@ public sealed class CryptoSigningKey
|
||||
|
||||
if (string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
|
||||
throw new ArgumentException(_t("crypto.key.algorithm_required"), nameof(algorithmId));
|
||||
}
|
||||
|
||||
if (privateKey.IsEmpty)
|
||||
{
|
||||
throw new ArgumentException("Private key material must be provided.", nameof(privateKey));
|
||||
throw new ArgumentException(_t("crypto.key.private_material_required"), nameof(privateKey));
|
||||
}
|
||||
|
||||
AlgorithmId = algorithmId;
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -63,7 +64,7 @@ public sealed class DefaultCryptoHash : ICryptoHash
|
||||
"GOST3411-2012-512" => GostDigestUtilities.ComputeDigest(data, use256: false),
|
||||
"BLAKE3-256" => ComputeBlake3(data),
|
||||
"SM3" => ComputeSm3(data),
|
||||
_ => throw new InvalidOperationException($"Unsupported hash algorithm '{algorithm}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.hash.algorithm_unsupported", algorithm))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,7 +94,7 @@ public sealed class DefaultCryptoHash : ICryptoHash
|
||||
"GOST3411-2012-512" => await ComputeGostStreamAsync(use256: false, stream, cancellationToken).ConfigureAwait(false),
|
||||
"BLAKE3-256" => await ComputeBlake3StreamAsync(stream, cancellationToken).ConfigureAwait(false),
|
||||
"SM3" => await ComputeSm3StreamAsync(stream, cancellationToken).ConfigureAwait(false),
|
||||
_ => throw new InvalidOperationException($"Unsupported hash algorithm '{algorithm}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.hash.algorithm_unsupported", algorithm))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -215,7 +216,7 @@ public sealed class DefaultCryptoHash : ICryptoHash
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(purpose))
|
||||
{
|
||||
throw new ArgumentException("Purpose cannot be null or empty.", nameof(purpose));
|
||||
throw new ArgumentException(_t("crypto.hash.purpose_required"), nameof(purpose));
|
||||
}
|
||||
|
||||
var opts = _complianceOptions.CurrentValue;
|
||||
@@ -237,7 +238,7 @@ public sealed class DefaultCryptoHash : ICryptoHash
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(purpose))
|
||||
{
|
||||
throw new ArgumentException("Purpose cannot be null or empty.", nameof(purpose));
|
||||
throw new ArgumentException(_t("crypto.hash.purpose_required"), nameof(purpose));
|
||||
}
|
||||
|
||||
var profile = GetActiveProfile();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -12,7 +13,7 @@ public sealed class DefaultCryptoHasher : ICryptoHasher
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(algorithmId))
|
||||
{
|
||||
throw new ArgumentException("Algorithm identifier is required.", nameof(algorithmId));
|
||||
throw new ArgumentException(_t("crypto.registry.algorithm_required"), nameof(algorithmId));
|
||||
}
|
||||
|
||||
AlgorithmId = algorithmId.ToUpperInvariant();
|
||||
@@ -27,7 +28,7 @@ public sealed class DefaultCryptoHasher : ICryptoHasher
|
||||
HashAlgorithms.Sha256 => SHA256.HashData(data),
|
||||
HashAlgorithms.Sha384 => SHA384.HashData(data),
|
||||
HashAlgorithms.Sha512 => SHA512.HashData(data),
|
||||
_ => throw new InvalidOperationException($"Unsupported hash algorithm '{AlgorithmId}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.hash.algorithm_unsupported", AlgorithmId))
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -136,7 +137,7 @@ public sealed class DefaultCryptoHmac : ICryptoHmac
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(purpose))
|
||||
{
|
||||
throw new ArgumentException("Purpose cannot be null or empty.", nameof(purpose));
|
||||
throw new ArgumentException(_t("crypto.hash.purpose_required"), nameof(purpose));
|
||||
}
|
||||
|
||||
var profile = GetActiveProfile();
|
||||
@@ -153,7 +154,7 @@ public sealed class DefaultCryptoHmac : ICryptoHmac
|
||||
"HMAC-SHA512" => 64,
|
||||
"HMAC-GOST3411" => 32, // GOST R 34.11-2012 Stribog-256
|
||||
"HMAC-SM3" => 32,
|
||||
_ => throw new InvalidOperationException($"Unknown HMAC algorithm '{algorithm}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.hmac.algorithm_unknown", algorithm))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -170,7 +171,7 @@ public sealed class DefaultCryptoHmac : ICryptoHmac
|
||||
"HMAC-SHA512" => ComputeHmacSha512(key, data),
|
||||
"HMAC-GOST3411" => ComputeHmacGost3411(key, data),
|
||||
"HMAC-SM3" => ComputeHmacSm3(key, data),
|
||||
_ => throw new InvalidOperationException($"Unsupported HMAC algorithm '{algorithm}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.hmac.algorithm_unsupported", algorithm))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -183,7 +184,7 @@ public sealed class DefaultCryptoHmac : ICryptoHmac
|
||||
"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}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.hmac.algorithm_unsupported", algorithm))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -237,7 +238,7 @@ public sealed class DefaultCryptoHmac : ICryptoHmac
|
||||
"SHA256" => (HMAC)new HMACSHA256(key.ToArray()),
|
||||
"SHA384" => new HMACSHA384(key.ToArray()),
|
||||
"SHA512" => new HMACSHA512(key.ToArray()),
|
||||
_ => throw new InvalidOperationException($"Unsupported hash algorithm '{name}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.hash.algorithm_unsupported", name))
|
||||
};
|
||||
|
||||
return await hmac.ComputeHashAsync(stream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -62,7 +63,7 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
{
|
||||
if (!Supports(CryptoCapability.PasswordHashing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Password hashing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.algorithm_not_supported", algorithmId, Name));
|
||||
}
|
||||
|
||||
return passwordHashers[algorithmId];
|
||||
@@ -72,7 +73,7 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
{
|
||||
if (!Supports(CryptoCapability.ContentHashing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Hash algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.hash_not_supported", algorithmId, Name));
|
||||
}
|
||||
|
||||
return new DefaultCryptoHasher(algorithmId);
|
||||
@@ -84,18 +85,18 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
|
||||
if (!Supports(CryptoCapability.Signing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.algorithm_not_supported", algorithmId, Name));
|
||||
}
|
||||
|
||||
if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey))
|
||||
{
|
||||
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
|
||||
throw new KeyNotFoundException(_t("crypto.provider.key_not_registered", keyReference.KeyId, Name));
|
||||
}
|
||||
|
||||
if (!string.Equals(signingKey.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'.");
|
||||
_t("crypto.provider.key_algorithm_mismatch", keyReference.KeyId, signingKey.AlgorithmId, algorithmId));
|
||||
}
|
||||
|
||||
return EcdsaSigner.Create(signingKey);
|
||||
@@ -105,7 +106,7 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
{
|
||||
if (!Supports(CryptoCapability.Verification, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Verification algorithm '{algorithmId}' is not supported by provider '{Name}'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.verify_not_supported", algorithmId, Name));
|
||||
}
|
||||
|
||||
return EcdsaSigner.CreateVerifierFromPublicKey(algorithmId, publicKeyBytes);
|
||||
@@ -117,7 +118,7 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
EnsureSigningSupported(signingKey.AlgorithmId);
|
||||
if (signingKey.Kind != CryptoSigningKeyKind.Ec)
|
||||
{
|
||||
throw new InvalidOperationException($"Provider '{Name}' only accepts EC signing keys.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.ec_keys_only", Name));
|
||||
}
|
||||
ValidateSigningKey(signingKey);
|
||||
|
||||
@@ -171,7 +172,7 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
{
|
||||
if (!SupportedSigningAlgorithms.Contains(algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider 'default'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.algorithm_not_supported", algorithmId, "default"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,14 +180,14 @@ public sealed class DefaultCryptoProvider : ICryptoProvider, ICryptoProviderDiag
|
||||
{
|
||||
if (!string.Equals(signingKey.AlgorithmId, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException($"Only ES256 signing keys are currently supported by provider 'default'.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.es256_only", "default"));
|
||||
}
|
||||
|
||||
var expected = ECCurve.NamedCurves.nistP256;
|
||||
var curve = signingKey.PrivateParameters.Curve;
|
||||
if (!curve.IsNamed || !string.Equals(curve.Oid.Value, expected.Oid.Value, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException("ES256 signing keys must use the NIST P-256 curve.");
|
||||
throw new InvalidOperationException(_t("crypto.provider.p256_required"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography.Digests;
|
||||
|
||||
/// <summary>
|
||||
@@ -20,7 +22,7 @@ public static class Sha256Digest
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(digest))
|
||||
{
|
||||
throw new ArgumentException("Digest is required.", parameterName ?? nameof(digest));
|
||||
throw new ArgumentException(_t("crypto.digest.required"), parameterName ?? nameof(digest));
|
||||
}
|
||||
|
||||
var trimmed = digest.Trim();
|
||||
@@ -33,11 +35,11 @@ public static class Sha256Digest
|
||||
else if (requirePrefix)
|
||||
{
|
||||
var name = string.IsNullOrWhiteSpace(parameterName) ? "Digest" : parameterName;
|
||||
throw new FormatException($"{name} must start with '{Prefix}'.");
|
||||
throw new FormatException(_t("crypto.digest.prefix_required", name, Prefix));
|
||||
}
|
||||
else if (trimmed.Contains(':', StringComparison.Ordinal))
|
||||
{
|
||||
throw new FormatException($"Unsupported digest algorithm in '{digest}'. Only sha256 is supported.");
|
||||
throw new FormatException(_t("crypto.digest.algorithm_unsupported", digest));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -48,7 +50,7 @@ public static class Sha256Digest
|
||||
if (hex.Length != HexLength || !IsHex(hex.AsSpan()))
|
||||
{
|
||||
var name = string.IsNullOrWhiteSpace(parameterName) ? "Digest" : parameterName;
|
||||
throw new FormatException($"{name} must contain {HexLength} hexadecimal characters.");
|
||||
throw new FormatException(_t("crypto.digest.hex_length", name, HexLength));
|
||||
}
|
||||
|
||||
return Prefix + hex.ToLowerInvariant();
|
||||
|
||||
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -87,7 +88,7 @@ public sealed class EcdsaSigner : ICryptoSigner
|
||||
{ } alg when string.Equals(alg, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase) => HashAlgorithmName.SHA256,
|
||||
{ } alg when string.Equals(alg, SignatureAlgorithms.Es384, StringComparison.OrdinalIgnoreCase) => HashAlgorithmName.SHA384,
|
||||
{ } alg when string.Equals(alg, SignatureAlgorithms.Es512, StringComparison.OrdinalIgnoreCase) => HashAlgorithmName.SHA512,
|
||||
_ => throw new InvalidOperationException($"Unsupported ECDSA signing algorithm '{algorithmId}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.ecdsa.algorithm_unsupported", algorithmId))
|
||||
};
|
||||
|
||||
private static string ResolveCurve(string algorithmId)
|
||||
@@ -96,6 +97,6 @@ public sealed class EcdsaSigner : ICryptoSigner
|
||||
{ } alg when string.Equals(alg, SignatureAlgorithms.Es256, StringComparison.OrdinalIgnoreCase) => JsonWebKeyECTypes.P256,
|
||||
{ } alg when string.Equals(alg, SignatureAlgorithms.Es384, StringComparison.OrdinalIgnoreCase) => JsonWebKeyECTypes.P384,
|
||||
{ } alg when string.Equals(alg, SignatureAlgorithms.Es512, StringComparison.OrdinalIgnoreCase) => JsonWebKeyECTypes.P521,
|
||||
_ => throw new InvalidOperationException($"Unsupported ECDSA curve mapping for algorithm '{algorithmId}'.")
|
||||
_ => throw new InvalidOperationException(_t("crypto.ecdsa.curve_unsupported", algorithmId))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Org.BouncyCastle.Asn1;
|
||||
using Org.BouncyCastle.Math;
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -41,13 +42,13 @@ public static class GostSignatureEncoding
|
||||
{
|
||||
if (!IsDer(der))
|
||||
{
|
||||
throw new CryptographicException("Signature is not DER encoded.");
|
||||
throw new CryptographicException(_t("crypto.gost.not_der"));
|
||||
}
|
||||
|
||||
var sequence = Asn1Sequence.GetInstance(Asn1Object.FromByteArray(der.ToArray()));
|
||||
if (sequence.Count != 2)
|
||||
{
|
||||
throw new CryptographicException("Invalid DER structure for GOST signature.");
|
||||
throw new CryptographicException(_t("crypto.gost.invalid_der"));
|
||||
}
|
||||
|
||||
var r = NormalizeCoordinate(((DerInteger)sequence[0]).PositiveValue.ToByteArrayUnsigned(), coordinateLength);
|
||||
@@ -63,7 +64,7 @@ public static class GostSignatureEncoding
|
||||
{
|
||||
if (raw.Length != coordinateLength * 2)
|
||||
{
|
||||
throw new CryptographicException($"Raw GOST signature must be {coordinateLength * 2} bytes.");
|
||||
throw new CryptographicException(_t("crypto.gost.raw_length", coordinateLength * 2));
|
||||
}
|
||||
|
||||
var s = raw[..coordinateLength].ToArray();
|
||||
@@ -83,7 +84,7 @@ public static class GostSignatureEncoding
|
||||
var sequence = Asn1Sequence.GetInstance(Asn1Object.FromByteArray(signature.ToArray()));
|
||||
if (sequence.Count != 2)
|
||||
{
|
||||
throw new CryptographicException("Invalid DER structure for GOST signature.");
|
||||
throw new CryptographicException(_t("crypto.gost.invalid_der"));
|
||||
}
|
||||
|
||||
return (((DerInteger)sequence[0]).PositiveValue, ((DerInteger)sequence[1]).PositiveValue);
|
||||
@@ -98,7 +99,7 @@ public static class GostSignatureEncoding
|
||||
return (new BigInteger(1, r), new BigInteger(1, s));
|
||||
}
|
||||
|
||||
throw new CryptographicException("Signature payload is neither DER nor raw GOST format.");
|
||||
throw new CryptographicException(_t("crypto.gost.neither_format"));
|
||||
}
|
||||
|
||||
private static byte[] NormalizeCoordinate(ReadOnlySpan<byte> value, int coordinateLength)
|
||||
@@ -106,7 +107,7 @@ public static class GostSignatureEncoding
|
||||
var trimmed = TrimLeadingZeros(value);
|
||||
if (trimmed.Length > coordinateLength)
|
||||
{
|
||||
throw new CryptographicException("Coordinate exceeds expected length.");
|
||||
throw new CryptographicException(_t("crypto.gost.coordinate_overflow"));
|
||||
}
|
||||
|
||||
var output = new byte[coordinateLength];
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -38,7 +39,7 @@ public sealed class LibsodiumCryptoProvider : ICryptoProvider
|
||||
}
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
||||
=> throw new NotSupportedException("Libsodium provider does not expose password hashing capabilities.");
|
||||
=> throw new NotSupportedException(_t("crypto.provider.no_password_hashing", Name));
|
||||
|
||||
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
||||
{
|
||||
@@ -48,13 +49,13 @@ public sealed class LibsodiumCryptoProvider : ICryptoProvider
|
||||
|
||||
if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey))
|
||||
{
|
||||
throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'.");
|
||||
throw new KeyNotFoundException(_t("crypto.provider.key_not_registered", keyReference.KeyId, Name));
|
||||
}
|
||||
|
||||
if (!string.Equals(signingKey.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'.");
|
||||
_t("crypto.provider.key_algorithm_mismatch", keyReference.KeyId, signingKey.AlgorithmId, algorithmId));
|
||||
}
|
||||
|
||||
return new LibsodiumEcdsaSigner(signingKey);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -44,17 +45,17 @@ public sealed record PasswordHashOptions
|
||||
{
|
||||
if (MemorySizeInKib <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Password hashing memory cost must be greater than zero.");
|
||||
throw new InvalidOperationException(_t("crypto.password.memory_cost_invalid"));
|
||||
}
|
||||
|
||||
if (Iterations <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Password hashing iteration count must be greater than zero.");
|
||||
throw new InvalidOperationException(_t("crypto.password.iterations_invalid"));
|
||||
}
|
||||
|
||||
if (Parallelism <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("Password hashing parallelism must be greater than zero.");
|
||||
throw new InvalidOperationException(_t("crypto.password.parallelism_invalid"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.Cryptography;
|
||||
|
||||
@@ -20,12 +21,12 @@ public sealed class Pbkdf2PasswordHasher : IPasswordHasher
|
||||
|
||||
if (options.Algorithm != PasswordHashAlgorithm.Pbkdf2)
|
||||
{
|
||||
throw new InvalidOperationException("Pbkdf2PasswordHasher only supports the PBKDF2 algorithm.");
|
||||
throw new InvalidOperationException(_t("crypto.password.algorithm_mismatch", "Pbkdf2PasswordHasher", "PBKDF2"));
|
||||
}
|
||||
|
||||
if (options.Iterations <= 0)
|
||||
{
|
||||
throw new InvalidOperationException("PBKDF2 requires a positive iteration count.");
|
||||
throw new InvalidOperationException(_t("crypto.password.pbkdf2_iterations"));
|
||||
}
|
||||
|
||||
Span<byte> salt = stackalloc byte[SaltLengthBytes];
|
||||
|
||||
@@ -16,4 +16,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Localization\StellaOps.Localization.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user