- 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.
151 lines
4.5 KiB
C#
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);
|
|
}
|