Files
git.stella-ops.org/src/Signer/__Libraries/StellaOps.Signer.Keyless/EphemeralKeyPair.cs
StellaOps Bot 907783f625 Add property-based tests for SBOM/VEX document ordering and Unicode normalization determinism
- Implement `SbomVexOrderingDeterminismProperties` for testing component list and vulnerability metadata hash consistency.
- Create `UnicodeNormalizationDeterminismProperties` to validate NFC normalization and Unicode string handling.
- Add project file for `StellaOps.Testing.Determinism.Properties` with necessary dependencies.
- Introduce CI/CD template validation tests including YAML syntax checks and documentation content verification.
- Create validation script for CI/CD templates ensuring all required files and structures are present.
2025-12-26 15:17:58 +02:00

151 lines
4.5 KiB
C#

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