using System.Runtime.CompilerServices; using System.Security.Cryptography; namespace StellaOps.Signer.Keyless; /// /// Represents an ephemeral keypair that exists only in memory. /// Private key material is securely erased on disposal. /// public sealed class EphemeralKeyPair : IDisposable { private byte[] _privateKey; private readonly byte[] _publicKey; private bool _disposed; /// /// The public key bytes. /// public ReadOnlySpan PublicKey => _publicKey; /// /// The private key bytes. Only accessible while not disposed. /// /// Thrown if accessed after disposal. public ReadOnlySpan PrivateKey { get { ObjectDisposedException.ThrowIf(_disposed, this); return _privateKey; } } /// /// The cryptographic algorithm used for this keypair. /// public string Algorithm { get; } /// /// The UTC timestamp when this keypair was created. /// public DateTimeOffset CreatedAt { get; } /// /// Creates a new ephemeral keypair. /// /// The public key bytes. /// The private key bytes (will be copied). /// The algorithm identifier. public EphemeralKeyPair(byte[] publicKey, byte[] privateKey, string algorithm) { ArgumentNullException.ThrowIfNull(publicKey); ArgumentNullException.ThrowIfNull(privateKey); ArgumentException.ThrowIfNullOrWhiteSpace(algorithm); _publicKey = (byte[])publicKey.Clone(); _privateKey = (byte[])privateKey.Clone(); Algorithm = algorithm; CreatedAt = DateTimeOffset.UtcNow; } /// /// Signs the specified data using the ephemeral private key. /// /// The data to sign. /// The signature bytes. /// Thrown if called after disposal. public byte[] Sign(ReadOnlySpan data) { ObjectDisposedException.ThrowIf(_disposed, this); return Algorithm switch { KeylessAlgorithms.EcdsaP256 => SignWithEcdsaP256(data), KeylessAlgorithms.Ed25519 => SignWithEd25519(data), _ => throw new NotSupportedException($"Unsupported algorithm: {Algorithm}") }; } private byte[] SignWithEcdsaP256(ReadOnlySpan data) { using var ecdsa = ECDsa.Create(); ecdsa.ImportECPrivateKey(_privateKey, out _); return ecdsa.SignData(data.ToArray(), HashAlgorithmName.SHA256); } private byte[] SignWithEd25519(ReadOnlySpan data) { // Ed25519 signing implementation // Note: .NET 9+ has native Ed25519 support via EdDSA throw new NotImplementedException("Ed25519 signing requires additional implementation"); } /// /// Securely disposes the keypair, zeroing all private key material. /// public void Dispose() { if (_disposed) return; // Zero out the private key memory if (_privateKey != null) { CryptographicOperations.ZeroMemory(_privateKey); _privateKey = []; } _disposed = true; GC.SuppressFinalize(this); } /// /// Finalizer ensures private key is zeroed if Dispose is not called. /// ~EphemeralKeyPair() { Dispose(); } } /// /// Well-known algorithm identifiers for keyless signing. /// public static class KeylessAlgorithms { /// /// ECDSA with P-256 curve (NIST P-256, secp256r1). /// public const string EcdsaP256 = "ECDSA_P256"; /// /// Edwards-curve Digital Signature Algorithm with Curve25519. /// public const string Ed25519 = "Ed25519"; /// /// All supported algorithms. /// public static readonly IReadOnlySet Supported = new HashSet(StringComparer.OrdinalIgnoreCase) { EcdsaP256, Ed25519 }; /// /// Validates that the specified algorithm is supported. /// public static bool IsSupported(string algorithm) => Supported.Contains(algorithm); }