- Implemented comprehensive tests for verdict artifact generation to ensure deterministic outputs across various scenarios, including identical inputs, parallel execution, and change ordering. - Created helper methods for generating sample verdict inputs and computing canonical hashes. - Added tests to validate the stability of canonical hashes, proof spine ordering, and summary statistics. - Introduced a new PowerShell script to update SHA256 sums for files, ensuring accurate hash generation and file integrity checks.
410 lines
14 KiB
C#
410 lines
14 KiB
C#
// -----------------------------------------------------------------------------
|
|
// CommandHandlers.Crypto.cs
|
|
// Sprint: SPRINT_4100_0006_0001 - Crypto Plugin CLI Architecture
|
|
// Description: Command handlers for cryptographic signing and verification.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Logging;
|
|
using Spectre.Console;
|
|
using StellaOps.Cryptography;
|
|
using StellaOps.Cryptography.Kms;
|
|
|
|
namespace StellaOps.Cli.Commands;
|
|
|
|
internal static partial class CommandHandlers
|
|
{
|
|
/// <summary>
|
|
/// Handle crypto sign command.
|
|
/// Signs artifacts using configured crypto provider with regional compliance support.
|
|
/// </summary>
|
|
internal static async Task<int> HandleCryptoSignAsync(
|
|
IServiceProvider services,
|
|
string input,
|
|
string? output,
|
|
string? providerName,
|
|
string? keyId,
|
|
string format,
|
|
bool detached,
|
|
bool verbose,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var logger = services.GetRequiredService<ILogger<object>>();
|
|
|
|
try
|
|
{
|
|
AnsiConsole.MarkupLine("[blue]Cryptographic Signing Operation[/]");
|
|
AnsiConsole.WriteLine();
|
|
|
|
// Validate input
|
|
if (!File.Exists(input))
|
|
{
|
|
AnsiConsole.MarkupLine($"[red]Error: Input file not found: {Markup.Escape(input)}[/]");
|
|
return 1;
|
|
}
|
|
|
|
output ??= $"{input}.sig";
|
|
|
|
// Display operation details
|
|
var table = new Table()
|
|
.Border(TableBorder.Rounded)
|
|
.AddColumn("Parameter")
|
|
.AddColumn("Value");
|
|
|
|
table.AddRow("Input", Markup.Escape(input));
|
|
table.AddRow("Output", Markup.Escape(output));
|
|
table.AddRow("Format", format);
|
|
table.AddRow("Detached", detached.ToString());
|
|
if (providerName != null) table.AddRow("Provider Override", providerName);
|
|
if (keyId != null) table.AddRow("Key ID", keyId);
|
|
|
|
AnsiConsole.Write(table);
|
|
AnsiConsole.WriteLine();
|
|
|
|
// Get crypto provider from DI
|
|
var cryptoProviders = services.GetServices<ICryptoProvider>().ToList();
|
|
|
|
if (cryptoProviders.Count == 0)
|
|
{
|
|
AnsiConsole.MarkupLine("[red]Error: No crypto providers available. Check your distribution and configuration.[/]");
|
|
AnsiConsole.MarkupLine("[yellow]Hint: Use 'stella crypto profiles' to list available providers.[/]");
|
|
return 1;
|
|
}
|
|
|
|
ICryptoProvider? provider = null;
|
|
|
|
if (providerName != null)
|
|
{
|
|
provider = cryptoProviders.FirstOrDefault(p => p.Name.Equals(providerName, StringComparison.OrdinalIgnoreCase));
|
|
if (provider == null)
|
|
{
|
|
AnsiConsole.MarkupLine($"[red]Error: Provider '{Markup.Escape(providerName)}' not found.[/]");
|
|
AnsiConsole.MarkupLine("[yellow]Available providers:[/]");
|
|
foreach (var p in cryptoProviders)
|
|
{
|
|
AnsiConsole.MarkupLine($" - {Markup.Escape(p.Name)}");
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
provider = cryptoProviders.First();
|
|
if (verbose)
|
|
{
|
|
AnsiConsole.MarkupLine($"[dim]Using default provider: {Markup.Escape(provider.Name)}[/]");
|
|
}
|
|
}
|
|
|
|
// Read input file
|
|
var inputData = await File.ReadAllBytesAsync(input, cancellationToken);
|
|
|
|
AnsiConsole.Status()
|
|
.Start("Signing...", ctx =>
|
|
{
|
|
ctx.Spinner(Spinner.Known.Dots);
|
|
ctx.SpinnerStyle(Style.Parse("blue"));
|
|
|
|
// Signing operation would happen here
|
|
// For now, this is a stub implementation
|
|
Thread.Sleep(500);
|
|
});
|
|
|
|
// Create stub signature
|
|
var signatureData = CreateStubSignature(inputData, format, provider.Name);
|
|
await File.WriteAllBytesAsync(output, signatureData, cancellationToken);
|
|
|
|
AnsiConsole.WriteLine();
|
|
AnsiConsole.MarkupLine("[green]✓ Signature created successfully[/]");
|
|
AnsiConsole.MarkupLine($" Signature: [bold]{Markup.Escape(output)}[/]");
|
|
AnsiConsole.MarkupLine($" Provider: {Markup.Escape(provider.Name)}");
|
|
AnsiConsole.MarkupLine($" Format: {format}");
|
|
|
|
if (verbose)
|
|
{
|
|
AnsiConsole.WriteLine();
|
|
AnsiConsole.MarkupLine($"[dim]Signature size: {signatureData.Length:N0} bytes[/]");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Crypto sign operation failed");
|
|
AnsiConsole.MarkupLine($"[red]Error: {Markup.Escape(ex.Message)}[/]");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle crypto verify command.
|
|
/// Verifies signatures using configured crypto provider.
|
|
/// </summary>
|
|
internal static async Task<int> HandleCryptoVerifyAsync(
|
|
IServiceProvider services,
|
|
string input,
|
|
string? signature,
|
|
string? providerName,
|
|
string? trustPolicy,
|
|
string? format,
|
|
bool verbose,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var logger = services.GetRequiredService<ILogger<object>>();
|
|
|
|
try
|
|
{
|
|
AnsiConsole.MarkupLine("[blue]Cryptographic Verification Operation[/]");
|
|
AnsiConsole.WriteLine();
|
|
|
|
// Validate input
|
|
if (!File.Exists(input))
|
|
{
|
|
AnsiConsole.MarkupLine($"[red]Error: Input file not found: {Markup.Escape(input)}[/]");
|
|
return 1;
|
|
}
|
|
|
|
signature ??= $"{input}.sig";
|
|
|
|
if (!File.Exists(signature))
|
|
{
|
|
AnsiConsole.MarkupLine($"[red]Error: Signature file not found: {Markup.Escape(signature)}[/]");
|
|
return 1;
|
|
}
|
|
|
|
// Display operation details
|
|
var table = new Table()
|
|
.Border(TableBorder.Rounded)
|
|
.AddColumn("Parameter")
|
|
.AddColumn("Value");
|
|
|
|
table.AddRow("Input", Markup.Escape(input));
|
|
table.AddRow("Signature", Markup.Escape(signature));
|
|
if (format != null) table.AddRow("Format", format);
|
|
if (providerName != null) table.AddRow("Provider Override", providerName);
|
|
if (trustPolicy != null) table.AddRow("Trust Policy", Markup.Escape(trustPolicy));
|
|
|
|
AnsiConsole.Write(table);
|
|
AnsiConsole.WriteLine();
|
|
|
|
// Get crypto provider from DI
|
|
var cryptoProviders = services.GetServices<ICryptoProvider>().ToList();
|
|
|
|
if (cryptoProviders.Count == 0)
|
|
{
|
|
AnsiConsole.MarkupLine("[red]Error: No crypto providers available. Check your distribution and configuration.[/]");
|
|
return 1;
|
|
}
|
|
|
|
ICryptoProvider? provider = null;
|
|
|
|
if (providerName != null)
|
|
{
|
|
provider = cryptoProviders.FirstOrDefault(p => p.Name.Equals(providerName, StringComparison.OrdinalIgnoreCase));
|
|
if (provider == null)
|
|
{
|
|
AnsiConsole.MarkupLine($"[red]Error: Provider '{Markup.Escape(providerName)}' not found.[/]");
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
provider = cryptoProviders.First();
|
|
if (verbose)
|
|
{
|
|
AnsiConsole.MarkupLine($"[dim]Using default provider: {Markup.Escape(provider.Name)}[/]");
|
|
}
|
|
}
|
|
|
|
// Read files
|
|
var inputData = await File.ReadAllBytesAsync(input, cancellationToken);
|
|
var signatureData = await File.ReadAllBytesAsync(signature, cancellationToken);
|
|
|
|
bool isValid = false;
|
|
|
|
AnsiConsole.Status()
|
|
.Start("Verifying signature...", ctx =>
|
|
{
|
|
ctx.Spinner(Spinner.Known.Dots);
|
|
ctx.SpinnerStyle(Style.Parse("blue"));
|
|
|
|
// Verification would happen here
|
|
// Stub implementation - always succeeds for now
|
|
Thread.Sleep(300);
|
|
isValid = true;
|
|
});
|
|
|
|
AnsiConsole.WriteLine();
|
|
|
|
if (isValid)
|
|
{
|
|
AnsiConsole.MarkupLine("[green]✓ Signature verification successful[/]");
|
|
AnsiConsole.MarkupLine($" Provider: {Markup.Escape(provider.Name)}");
|
|
|
|
if (verbose)
|
|
{
|
|
AnsiConsole.WriteLine();
|
|
AnsiConsole.MarkupLine("[dim]Signature Details:[/]");
|
|
AnsiConsole.MarkupLine($"[dim] Algorithm: STUB-ALGORITHM[/]");
|
|
AnsiConsole.MarkupLine($"[dim] Key ID: STUB-KEY-ID[/]");
|
|
AnsiConsole.MarkupLine($"[dim] Timestamp: {DateTimeOffset.UtcNow:O}[/]");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
AnsiConsole.MarkupLine("[red]✗ Signature verification failed[/]");
|
|
return 1;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Crypto verify operation failed");
|
|
AnsiConsole.MarkupLine($"[red]Error: {Markup.Escape(ex.Message)}[/]");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handle crypto profiles command.
|
|
/// Lists available crypto providers and their capabilities.
|
|
/// </summary>
|
|
internal static async Task<int> HandleCryptoProfilesAsync(
|
|
IServiceProvider services,
|
|
bool showDetails,
|
|
string? providerFilter,
|
|
bool test,
|
|
bool verbose,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var logger = services.GetRequiredService<ILogger<object>>();
|
|
|
|
try
|
|
{
|
|
AnsiConsole.MarkupLine("[blue]Available Cryptographic Providers[/]");
|
|
AnsiConsole.WriteLine();
|
|
|
|
// Get crypto providers from DI
|
|
var cryptoProviders = services.GetServices<ICryptoProvider>().ToList();
|
|
|
|
if (providerFilter != null)
|
|
{
|
|
cryptoProviders = cryptoProviders
|
|
.Where(p => p.Name.Contains(providerFilter, StringComparison.OrdinalIgnoreCase))
|
|
.ToList();
|
|
}
|
|
|
|
if (cryptoProviders.Count == 0)
|
|
{
|
|
if (providerFilter != null)
|
|
{
|
|
AnsiConsole.MarkupLine($"[yellow]No providers matching '{Markup.Escape(providerFilter)}' found.[/]");
|
|
}
|
|
else
|
|
{
|
|
AnsiConsole.MarkupLine("[yellow]No crypto providers available.[/]");
|
|
AnsiConsole.WriteLine();
|
|
AnsiConsole.MarkupLine("[dim]This may indicate:[/]");
|
|
AnsiConsole.MarkupLine("[dim] • You are using the international distribution (GOST/eIDAS/SM disabled)[/]");
|
|
AnsiConsole.MarkupLine("[dim] • Crypto plugins are not properly configured[/]");
|
|
AnsiConsole.MarkupLine("[dim] • Build-time distribution flags were not set[/]");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// Display providers
|
|
foreach (var provider in cryptoProviders)
|
|
{
|
|
var panel = new Panel(CreateProviderTable(provider, showDetails, test))
|
|
.Header($"[bold]{Markup.Escape(provider.Name)}[/]")
|
|
.Border(BoxBorder.Rounded)
|
|
.BorderColor(Color.Blue);
|
|
|
|
AnsiConsole.Write(panel);
|
|
AnsiConsole.WriteLine();
|
|
}
|
|
|
|
// Display distribution info
|
|
AnsiConsole.MarkupLine("[dim]Distribution Information:[/]");
|
|
|
|
var distributionTable = new Table()
|
|
.Border(TableBorder.Rounded)
|
|
.AddColumn("Feature")
|
|
.AddColumn("Status");
|
|
|
|
#if STELLAOPS_ENABLE_GOST
|
|
distributionTable.AddRow("GOST (Russia)", "[green]Enabled[/]");
|
|
#else
|
|
distributionTable.AddRow("GOST (Russia)", "[dim]Disabled[/]");
|
|
#endif
|
|
|
|
#if STELLAOPS_ENABLE_EIDAS
|
|
distributionTable.AddRow("eIDAS (EU)", "[green]Enabled[/]");
|
|
#else
|
|
distributionTable.AddRow("eIDAS (EU)", "[dim]Disabled[/]");
|
|
#endif
|
|
|
|
#if STELLAOPS_ENABLE_SM
|
|
distributionTable.AddRow("SM (China)", "[green]Enabled[/]");
|
|
#else
|
|
distributionTable.AddRow("SM (China)", "[dim]Disabled[/]");
|
|
#endif
|
|
|
|
distributionTable.AddRow("BouncyCastle", "[green]Enabled[/]");
|
|
|
|
AnsiConsole.Write(distributionTable);
|
|
|
|
return 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Crypto profiles operation failed");
|
|
AnsiConsole.MarkupLine($"[red]Error: {Markup.Escape(ex.Message)}[/]");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
private static Table CreateProviderTable(ICryptoProvider provider, bool showDetails, bool runTests)
|
|
{
|
|
var table = new Table()
|
|
.Border(TableBorder.None)
|
|
.HideHeaders()
|
|
.AddColumn("Property")
|
|
.AddColumn("Value");
|
|
|
|
table.AddRow("[dim]Provider Name:[/]", Markup.Escape(provider.Name));
|
|
table.AddRow("[dim]Status:[/]", "[green]Available[/]");
|
|
|
|
if (showDetails)
|
|
{
|
|
table.AddRow("[dim]Type:[/]", provider.GetType().Name);
|
|
}
|
|
|
|
if (runTests)
|
|
{
|
|
table.AddRow("[dim]Diagnostics:[/]", "[yellow]Test mode not yet implemented[/]");
|
|
}
|
|
|
|
return table;
|
|
}
|
|
|
|
private static byte[] CreateStubSignature(byte[] data, string format, string providerName)
|
|
{
|
|
// Stub implementation - creates a JSON signature envelope
|
|
var signature = new
|
|
{
|
|
format = format,
|
|
provider = providerName,
|
|
timestamp = DateTimeOffset.UtcNow.ToString("O"),
|
|
dataHash = CryptoHashFactory.CreateDefault().ComputeHashHex(data, HashAlgorithms.Sha256),
|
|
signature = "STUB-SIGNATURE-BASE64",
|
|
keyId = "STUB-KEY-ID"
|
|
};
|
|
|
|
return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(signature, new JsonSerializerOptions { WriteIndented = true }));
|
|
}
|
|
}
|