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