search and ai stabilization work, localization stablized.

This commit is contained in:
master
2026-02-24 23:29:36 +02:00
parent 4f947a8b61
commit b07d27772e
766 changed files with 55299 additions and 3221 deletions

View File

@@ -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];

View File

@@ -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);

View File

@@ -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>

View File

@@ -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.

View File

@@ -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);

View File

@@ -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;

View File

@@ -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();

View File

@@ -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))
};
}

View File

@@ -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);

View File

@@ -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"));
}
}
}

View File

@@ -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();

View File

@@ -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))
};
}

View File

@@ -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];

View File

@@ -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);

View File

@@ -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"));
}
}
}

View File

@@ -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];

View File

@@ -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>