using Org.BouncyCastle.Crypto.Parameters; using StellaOps.Cryptography; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using static StellaOps.Localization.T; namespace StellaOps.Cryptography.Plugin.BouncyCastle; /// /// Ed25519 signing provider backed by BouncyCastle primitives. /// public sealed partial class BouncyCastleEd25519CryptoProvider : ICryptoProvider { private static readonly HashSet _supportedAlgorithms = new(StringComparer.OrdinalIgnoreCase) { SignatureAlgorithms.Ed25519, SignatureAlgorithms.EdDsa }; private static readonly string[] _defaultKeyOps = { "sign", "verify" }; private readonly ConcurrentDictionary _signingKeys = new(StringComparer.Ordinal); public string Name => "bouncycastle.ed25519"; public bool Supports(CryptoCapability capability, string algorithmId) { if (string.IsNullOrWhiteSpace(algorithmId)) { return false; } return capability switch { CryptoCapability.Signing or CryptoCapability.Verification => _supportedAlgorithms.Contains(algorithmId), _ => false }; } public ICryptoHasher GetHasher(string algorithmId) => throw new NotSupportedException(_t("crypto.ed25519.no_hashing")); public IPasswordHasher GetPasswordHasher(string algorithmId) => throw new NotSupportedException(_t("crypto.provider.no_password_hashing", Name)); public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference) { ArgumentException.ThrowIfNullOrWhiteSpace(algorithmId); ArgumentNullException.ThrowIfNull(keyReference); if (!_signingKeys.TryGetValue(keyReference.KeyId, out var entry)) { throw new KeyNotFoundException(_t("crypto.provider.key_not_registered", keyReference.KeyId, Name)); } EnsureAlgorithmSupported(algorithmId); var normalized = NormalizeAlgorithm(algorithmId); if (!string.Equals(entry.Descriptor.AlgorithmId, normalized, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException( _t("crypto.provider.key_algorithm_mismatch", keyReference.KeyId, entry.Descriptor.AlgorithmId, algorithmId)); } return new Ed25519SignerWrapper(entry); } public void UpsertSigningKey(CryptoSigningKey signingKey) { ArgumentNullException.ThrowIfNull(signingKey); EnsureAlgorithmSupported(signingKey.AlgorithmId); if (signingKey.Kind != CryptoSigningKeyKind.Raw) { throw new InvalidOperationException(_t("crypto.ed25519.raw_key_required", Name)); } var privateKey = NormalizePrivateKey(signingKey.PrivateKey); var publicKey = NormalizePublicKey(signingKey.PublicKey, privateKey); var privateKeyParameters = new Ed25519PrivateKeyParameters(privateKey, 0); var publicKeyParameters = new Ed25519PublicKeyParameters(publicKey, 0); var descriptor = new CryptoSigningKey( signingKey.Reference, NormalizeAlgorithm(signingKey.AlgorithmId), privateKey, signingKey.CreatedAt, signingKey.ExpiresAt, publicKey, signingKey.Metadata); _signingKeys.AddOrUpdate( signingKey.Reference.KeyId, _ => new KeyEntry(descriptor, privateKeyParameters, publicKeyParameters), (_, _) => new KeyEntry(descriptor, privateKeyParameters, publicKeyParameters)); } public bool RemoveSigningKey(string keyId) { if (string.IsNullOrWhiteSpace(keyId)) { return false; } return _signingKeys.TryRemove(keyId, out _); } public IReadOnlyCollection GetSigningKeys() => _signingKeys.Values.Select(static entry => entry.Descriptor).ToArray(); }