#if STELLAOPS_CRYPTO_SODIUM using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.Tokens; namespace StellaOps.Cryptography; /// /// Libsodium-backed crypto provider (ES256) registered when STELLAOPS_CRYPTO_SODIUM is defined. /// public sealed class LibsodiumCryptoProvider : ICryptoProvider { private static readonly HashSet SupportedAlgorithms = new(StringComparer.OrdinalIgnoreCase) { SignatureAlgorithms.Es256 }; private readonly ConcurrentDictionary signingKeys = new(StringComparer.Ordinal); public string Name => "libsodium"; 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 IPasswordHasher GetPasswordHasher(string algorithmId) => throw new NotSupportedException("Libsodium provider does not expose password hashing capabilities."); public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference) { ArgumentNullException.ThrowIfNull(keyReference); EnsureAlgorithmSupported(algorithmId); if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey)) { throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'."); } if (!string.Equals(signingKey.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException( $"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'."); } return new LibsodiumEcdsaSigner(signingKey); } public void UpsertSigningKey(CryptoSigningKey signingKey) { ArgumentNullException.ThrowIfNull(signingKey); EnsureAlgorithmSupported(signingKey.AlgorithmId); if (signingKey.Kind != CryptoSigningKeyKind.Ec) { throw new InvalidOperationException($"Provider '{Name}' only accepts EC signing keys."); } signingKeys.AddOrUpdate(signingKey.Reference.KeyId, signingKey, (_, _) => signingKey); } public bool RemoveSigningKey(string keyId) { if (string.IsNullOrWhiteSpace(keyId)) { return false; } return signingKeys.TryRemove(keyId, out _); } public IReadOnlyCollection GetSigningKeys() => signingKeys.Values.ToArray(); private static void EnsureAlgorithmSupported(string algorithmId) { if (!SupportedAlgorithms.Contains(algorithmId)) { throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider 'libsodium'."); } } private sealed class LibsodiumEcdsaSigner : ICryptoSigner { private readonly CryptoSigningKey signingKey; private readonly ICryptoSigner fallbackSigner; public LibsodiumEcdsaSigner(CryptoSigningKey signingKey) { this.signingKey = signingKey ?? throw new ArgumentNullException(nameof(signingKey)); fallbackSigner = EcdsaSigner.Create(signingKey); } public string KeyId => signingKey.Reference.KeyId; public string AlgorithmId => signingKey.AlgorithmId; public ValueTask SignAsync(ReadOnlyMemory data, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); // TODO(SEC5.B1): replace fallback with libsodium bindings once native interop lands. return fallbackSigner.SignAsync(data, cancellationToken); } public ValueTask VerifyAsync(ReadOnlyMemory data, ReadOnlyMemory signature, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); return fallbackSigner.VerifyAsync(data, signature, cancellationToken); } public JsonWebKey ExportPublicJsonWebKey() => fallbackSigner.ExportPublicJsonWebKey(); } } #endif