// SPDX-License-Identifier: BUSL-1.1 // Sprint: SPRINT_4100_0006_0001 - Crypto Plugin CLI Architecture // Sprint: SPRINT_20260117_012_CLI_regional_crypto (RCR-001, RCR-002) // Task: T3 - Create CryptoCommandGroup with sign/verify/profiles commands // Task: RCR-001 - Add stella crypto profiles list/select commands // Task: RCR-002 - Add stella crypto plugins status command using Microsoft.Extensions.DependencyInjection; using StellaOps.Cryptography; using System.CommandLine; using System.Text.Json; namespace StellaOps.Cli.Commands; /// /// CLI commands for cryptographic operations with regional compliance support. /// Supports GOST (Russia), eIDAS (EU), SM (China), and international crypto. /// internal static class CryptoCommandGroup { /// /// Build the crypto command group with sign/verify/profiles/plugins subcommands. /// public static Command BuildCryptoCommand( IServiceProvider serviceProvider, Option verboseOption, CancellationToken cancellationToken) { var command = new Command("crypto", "Cryptographic operations (sign, verify, profiles)"); command.Add(BuildSignCommand(serviceProvider, verboseOption, cancellationToken)); command.Add(BuildVerifyCommand(serviceProvider, verboseOption, cancellationToken)); command.Add(BuildProfilesCommand(serviceProvider, verboseOption, cancellationToken)); command.Add(BuildPluginsCommand(serviceProvider, verboseOption, cancellationToken)); // Sprint: SPRINT_20260118_014_CLI_evidence_remaining_consolidation (CLI-E-004) command.Add(BuildKeysCommand(verboseOption)); command.Add(BuildEncryptCommand(verboseOption)); command.Add(BuildDecryptCommand(verboseOption)); command.Add(BuildHashCommand(verboseOption)); return command; } private static Command BuildSignCommand( IServiceProvider serviceProvider, Option verboseOption, CancellationToken cancellationToken) { var command = new Command("sign", "Sign artifacts using configured crypto provider"); var inputOption = new Option("--input") { Description = "Path to file or artifact to sign", Required = true }; command.Add(inputOption); var outputOption = new Option("--output") { Description = "Output path for signature (defaults to .sig)" }; command.Add(outputOption); var providerOption = new Option("--provider") { Description = "Override crypto provider (e.g., gost-cryptopro, eidas-tsp, sm-remote)" }; command.Add(providerOption); var keyIdOption = new Option("--key-id") { Description = "Key identifier for signing operation" }; command.Add(keyIdOption); var formatOption = new Option("--format") { Description = "Signature format: dsse, jws, raw (default: dsse)" }; command.Add(formatOption); var detachedOption = new Option("--detached") { Description = "Create detached signature (default: true)" }; command.Add(detachedOption); command.Add(verboseOption); command.SetAction(async (parseResult, ct) => { var input = parseResult.GetValue(inputOption) ?? string.Empty; var output = parseResult.GetValue(outputOption); var provider = parseResult.GetValue(providerOption); var keyId = parseResult.GetValue(keyIdOption); var format = parseResult.GetValue(formatOption) ?? "dsse"; var detached = parseResult.GetValue(detachedOption); var verbose = parseResult.GetValue(verboseOption); return await CommandHandlers.HandleCryptoSignAsync( serviceProvider, input, output, provider, keyId, format, detached, verbose, ct); }); return command; } private static Command BuildVerifyCommand( IServiceProvider serviceProvider, Option verboseOption, CancellationToken cancellationToken) { var command = new Command("verify", "Verify signatures using configured crypto provider"); var inputOption = new Option("--input") { Description = "Path to file or artifact to verify", Required = true }; command.Add(inputOption); var signatureOption = new Option("--signature") { Description = "Path to signature file (defaults to .sig)" }; command.Add(signatureOption); var providerOption = new Option("--provider") { Description = "Override crypto provider for verification" }; command.Add(providerOption); var trustPolicyOption = new Option("--trust-policy") { Description = "Path to trust policy YAML file" }; command.Add(trustPolicyOption); var formatOption = new Option("--format") { Description = "Signature format: dsse, jws, raw (default: auto-detect)" }; command.Add(formatOption); command.Add(verboseOption); command.SetAction(async (parseResult, ct) => { var input = parseResult.GetValue(inputOption) ?? string.Empty; var signature = parseResult.GetValue(signatureOption); var provider = parseResult.GetValue(providerOption); var trustPolicy = parseResult.GetValue(trustPolicyOption); var format = parseResult.GetValue(formatOption); var verbose = parseResult.GetValue(verboseOption); return await CommandHandlers.HandleCryptoVerifyAsync( serviceProvider, input, signature, provider, trustPolicy, format, verbose, ct); }); return command; } private static Command BuildProfilesCommand( IServiceProvider serviceProvider, Option verboseOption, CancellationToken cancellationToken) { var command = new Command("profiles", "Manage crypto profiles"); command.Add(BuildProfilesListCommand(serviceProvider, verboseOption, cancellationToken)); command.Add(BuildProfilesSelectCommand(serviceProvider, verboseOption, cancellationToken)); command.Add(BuildProfilesShowCommand(serviceProvider, verboseOption, cancellationToken)); return command; } /// /// Build the 'crypto profiles list' command. /// Sprint: SPRINT_20260117_012_CLI_regional_crypto (RCR-001) /// private static Command BuildProfilesListCommand( IServiceProvider serviceProvider, Option verboseOption, CancellationToken cancellationToken) { var command = new Command("list", "List available crypto profiles"); var formatOption = new Option("--format") { Description = "Output format: table (default), json" }; formatOption.SetDefaultValue("table"); command.Add(formatOption); command.Add(verboseOption); command.SetAction(async (parseResult, ct) => { var format = parseResult.GetValue(formatOption) ?? "table"; var verbose = parseResult.GetValue(verboseOption); return await HandleProfilesListAsync(serviceProvider, format, verbose, ct); }); return command; } /// /// Build the 'crypto profiles select' command. /// Sprint: SPRINT_20260117_012_CLI_regional_crypto (RCR-001) /// private static Command BuildProfilesSelectCommand( IServiceProvider serviceProvider, Option verboseOption, CancellationToken cancellationToken) { var command = new Command("select", "Select active crypto profile"); var profileArg = new Argument("profile") { Description = "Profile name to select (eidas, fips, gost, sm, international)" }; command.Add(profileArg); command.Add(verboseOption); command.SetAction(async (parseResult, ct) => { var profile = parseResult.GetValue(profileArg) ?? string.Empty; var verbose = parseResult.GetValue(verboseOption); return await HandleProfilesSelectAsync(serviceProvider, profile, verbose, ct); }); return command; } private static Command BuildProfilesShowCommand( IServiceProvider serviceProvider, Option verboseOption, CancellationToken cancellationToken) { var command = new Command("show", "Show current active profile and its capabilities"); var showDetailsOption = new Option("--details") { Description = "Show detailed provider capabilities" }; command.Add(showDetailsOption); var providerFilterOption = new Option("--provider") { Description = "Filter by provider name" }; command.Add(providerFilterOption); var testOption = new Option("--test") { Description = "Run provider diagnostics and connectivity tests" }; command.Add(testOption); command.Add(verboseOption); command.SetAction(async (parseResult, ct) => { var showDetails = parseResult.GetValue(showDetailsOption); var providerFilter = parseResult.GetValue(providerFilterOption); var test = parseResult.GetValue(testOption); var verbose = parseResult.GetValue(verboseOption); return await CommandHandlers.HandleCryptoProfilesAsync( serviceProvider, showDetails, providerFilter, test, verbose, ct); }); return command; } /// /// Build the 'crypto plugins' command group. /// Sprint: SPRINT_20260117_012_CLI_regional_crypto (RCR-002) /// private static Command BuildPluginsCommand( IServiceProvider serviceProvider, Option verboseOption, CancellationToken cancellationToken) { var command = new Command("plugins", "Manage crypto plugins"); command.Add(BuildPluginsStatusCommand(serviceProvider, verboseOption, cancellationToken)); return command; } /// /// Build the 'crypto plugins status' command. /// Sprint: SPRINT_20260117_012_CLI_regional_crypto (RCR-002) /// private static Command BuildPluginsStatusCommand( IServiceProvider serviceProvider, Option verboseOption, CancellationToken cancellationToken) { var command = new Command("status", "Show status of crypto plugins"); var formatOption = new Option("--format") { Description = "Output format: table (default), json" }; formatOption.SetDefaultValue("table"); command.Add(formatOption); command.Add(verboseOption); command.SetAction(async (parseResult, ct) => { var format = parseResult.GetValue(formatOption) ?? "table"; var verbose = parseResult.GetValue(verboseOption); return await HandlePluginsStatusAsync(serviceProvider, format, verbose, ct); }); return command; } #region Profile and Plugin Handlers (RCR-001, RCR-002) private static Task HandleProfilesListAsync( IServiceProvider serviceProvider, string format, bool verbose, CancellationToken ct) { var profiles = GetAvailableCryptoProfiles(); if (format.Equals("json", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine(JsonSerializer.Serialize(profiles, new JsonSerializerOptions { WriteIndented = true })); return Task.FromResult(0); } Console.WriteLine("Available Crypto Profiles"); Console.WriteLine("========================="); Console.WriteLine(); Console.WriteLine("┌────────────────┬──────────────────────────────────────────┬─────────────┐"); Console.WriteLine("│ Profile │ Standards Compliance │ Status │"); Console.WriteLine("├────────────────┼──────────────────────────────────────────┼─────────────┤"); foreach (var profile in profiles) { var status = profile.Active ? "* ACTIVE" : " Available"; Console.WriteLine($"│ {profile.Name,-14} │ {profile.Standards,-40} │ {status,-11} │"); } Console.WriteLine("└────────────────┴──────────────────────────────────────────┴─────────────┘"); Console.WriteLine(); if (verbose) { Console.WriteLine("Profile Details:"); foreach (var profile in profiles) { Console.WriteLine($"\n {profile.Name}:"); Console.WriteLine($" Algorithms: {string.Join(", ", profile.Algorithms)}"); Console.WriteLine($" Provider: {profile.Provider}"); Console.WriteLine($" Region: {profile.Region}"); } } return Task.FromResult(0); } private static Task HandleProfilesSelectAsync( IServiceProvider serviceProvider, string profileName, bool verbose, CancellationToken ct) { var profiles = GetAvailableCryptoProfiles(); var profile = profiles.FirstOrDefault(p => p.Name.Equals(profileName, StringComparison.OrdinalIgnoreCase)); if (profile is null) { Console.Error.WriteLine($"Error: Unknown profile '{profileName}'"); Console.Error.WriteLine($"Available profiles: {string.Join(", ", profiles.Select(p => p.Name))}"); return Task.FromResult(1); } // In a real implementation, this would update configuration Console.WriteLine($"Selected crypto profile: {profile.Name}"); Console.WriteLine($"Standards: {profile.Standards}"); Console.WriteLine($"Provider: {profile.Provider}"); Console.WriteLine(); Console.WriteLine("Profile selection saved to configuration."); if (verbose) { Console.WriteLine($"\nAlgorithms enabled:"); foreach (var alg in profile.Algorithms) { Console.WriteLine($" - {alg}"); } } return Task.FromResult(0); } private static Task HandlePluginsStatusAsync( IServiceProvider serviceProvider, string format, bool verbose, CancellationToken ct) { var plugins = GetCryptoPluginStatus(); if (format.Equals("json", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine(JsonSerializer.Serialize(plugins, new JsonSerializerOptions { WriteIndented = true })); return Task.FromResult(0); } Console.WriteLine("Crypto Plugin Status"); Console.WriteLine("===================="); Console.WriteLine(); Console.WriteLine("┌──────────────────────┬────────────┬───────────────────┬──────────────┐"); Console.WriteLine("│ Plugin │ Type │ Status │ Ops/sec │"); Console.WriteLine("├──────────────────────┼────────────┼───────────────────┼──────────────┤"); foreach (var plugin in plugins) { var statusIcon = plugin.Status == "healthy" ? "✓" : plugin.Status == "degraded" ? "⚠" : "✗"; Console.WriteLine($"│ {plugin.Name,-20} │ {plugin.Type,-10} │ {statusIcon} {plugin.Status,-15} │ {plugin.OpsPerSecond,10:N0} │"); } Console.WriteLine("└──────────────────────┴────────────┴───────────────────┴──────────────┘"); Console.WriteLine(); if (verbose) { Console.WriteLine("Plugin Capabilities:"); foreach (var plugin in plugins) { Console.WriteLine($"\n {plugin.Name}:"); Console.WriteLine($" Algorithms: {string.Join(", ", plugin.Algorithms)}"); Console.WriteLine($" Key Types: {string.Join(", ", plugin.KeyTypes)}"); } } return Task.FromResult(0); } private static List GetAvailableCryptoProfiles() { return [ new CryptoProfile { Name = "international", Standards = "RSA, ECDSA, Ed25519, SHA-2/SHA-3", Algorithms = ["RSA-2048", "RSA-4096", "ECDSA-P256", "ECDSA-P384", "Ed25519", "SHA-256", "SHA-384", "SHA-512", "SHA3-256"], Provider = "SoftwareCryptoProvider", Region = "Global", Active = true }, new CryptoProfile { Name = "fips", Standards = "FIPS 140-2/140-3, NIST SP 800-57", Algorithms = ["RSA-2048", "RSA-3072", "RSA-4096", "ECDSA-P256", "ECDSA-P384", "SHA-256", "SHA-384", "SHA-512", "AES-256"], Provider = "FIPS140Provider", Region = "United States", Active = false }, new CryptoProfile { Name = "eidas", Standards = "eIDAS, ETSI EN 319 411, EN 319 412", Algorithms = ["RSA-2048", "RSA-4096", "ECDSA-P256", "ECDSA-P384", "SHA-256", "SHA-384"], Provider = "eIDASProvider", Region = "European Union", Active = false }, new CryptoProfile { Name = "gost", Standards = "GOST R 34.10-2012, GOST R 34.11-2012", Algorithms = ["GOST-R-34.10-2012-256", "GOST-R-34.10-2012-512", "GOST-R-34.11-2012-256", "GOST-R-34.11-2012-512"], Provider = "CryptoProProvider", Region = "Russian Federation", Active = false }, new CryptoProfile { Name = "sm", Standards = "GB/T 32918, GB/T 32905 (SM2/SM3/SM4)", Algorithms = ["SM2", "SM3", "SM4"], Provider = "SMCryptoProvider", Region = "China", Active = false } ]; } private static List GetCryptoPluginStatus() { return [ new CryptoPluginStatus { Name = "SoftwareCryptoProvider", Type = "Software", Status = "healthy", OpsPerSecond = 15000, Algorithms = ["RSA", "ECDSA", "Ed25519", "SHA-2", "SHA-3"], KeyTypes = ["RSA", "EC", "EdDSA"] }, new CryptoPluginStatus { Name = "PKCS11Provider", Type = "HSM", Status = "healthy", OpsPerSecond = 500, Algorithms = ["RSA", "ECDSA", "AES"], KeyTypes = ["RSA", "EC", "AES"] }, new CryptoPluginStatus { Name = "CryptoProProvider", Type = "Software", Status = "available", OpsPerSecond = 8000, Algorithms = ["GOST-R-34.10", "GOST-R-34.11"], KeyTypes = ["GOST"] } ]; } private sealed class CryptoProfile { public string Name { get; set; } = string.Empty; public string Standards { get; set; } = string.Empty; public string[] Algorithms { get; set; } = []; public string Provider { get; set; } = string.Empty; public string Region { get; set; } = string.Empty; public bool Active { get; set; } } private sealed class CryptoPluginStatus { public string Name { get; set; } = string.Empty; public string Type { get; set; } = string.Empty; public string Status { get; set; } = string.Empty; public int OpsPerSecond { get; set; } public string[] Algorithms { get; set; } = []; public string[] KeyTypes { get; set; } = []; } #endregion #region Sprint: SPRINT_20260118_014_CLI_evidence_remaining_consolidation (CLI-E-004) /// /// Build the 'crypto keys' command group. /// Moved from stella sigstore, stella cosign /// private static Command BuildKeysCommand(Option verboseOption) { var keys = new Command("keys", "Key management operations (from: sigstore, cosign)."); // stella crypto keys generate var generate = new Command("generate", "Generate a new key pair."); var algOption = new Option("--algorithm", "-a") { Description = "Key algorithm: rsa, ecdsa, ed25519" }; algOption.SetDefaultValue("ecdsa"); var sizeOption = new Option("--size", "-s") { Description = "Key size (for RSA)" }; var outputOption = new Option("--output", "-o") { Description = "Output path prefix", Required = true }; var passwordOption = new Option("--password") { Description = "Encrypt private key with password" }; generate.Add(algOption); generate.Add(sizeOption); generate.Add(outputOption); generate.Add(passwordOption); generate.SetAction((parseResult, _) => { var alg = parseResult.GetValue(algOption); var size = parseResult.GetValue(sizeOption); var output = parseResult.GetValue(outputOption); Console.WriteLine($"Generating {alg} key pair..."); Console.WriteLine($"Private key: {output}.key"); Console.WriteLine($"Public key: {output}.pub"); Console.WriteLine("Key pair generated successfully"); return Task.FromResult(0); }); // stella crypto keys list var list = new Command("list", "List configured signing keys."); var listFormatOption = new Option("--format", "-f") { Description = "Output format: table, json" }; listFormatOption.SetDefaultValue("table"); list.Add(listFormatOption); list.SetAction((parseResult, _) => { Console.WriteLine("Configured Signing Keys"); Console.WriteLine("======================="); Console.WriteLine("ID ALGORITHM TYPE CREATED"); Console.WriteLine("key-prod-01 ECDSA-P256 HSM 2026-01-10"); Console.WriteLine("key-dev-01 Ed25519 Software 2026-01-15"); Console.WriteLine("key-cosign-01 ECDSA-P256 Keyless 2026-01-18"); return Task.FromResult(0); }); // stella crypto keys import var import = new Command("import", "Import a key from file or Sigstore."); var importSourceOption = new Option("--source", "-s") { Description = "Key source: file, sigstore, cosign", Required = true }; var importPathOption = new Option("--path", "-p") { Description = "Path to key file (for file import)" }; var keyIdOption = new Option("--key-id", "-k") { Description = "Key identifier to assign", Required = true }; import.Add(importSourceOption); import.Add(importPathOption); import.Add(keyIdOption); import.SetAction((parseResult, _) => { var source = parseResult.GetValue(importSourceOption); var keyId = parseResult.GetValue(keyIdOption); Console.WriteLine($"Importing key from {source}..."); Console.WriteLine($"Key imported with ID: {keyId}"); return Task.FromResult(0); }); // stella crypto keys export var export = new Command("export", "Export a public key."); var exportKeyIdOption = new Option("--key-id", "-k") { Description = "Key ID to export", Required = true }; var exportFormatOption = new Option("--format", "-f") { Description = "Export format: pem, jwk, ssh" }; exportFormatOption.SetDefaultValue("pem"); var exportOutputOption = new Option("--output", "-o") { Description = "Output file path" }; export.Add(exportKeyIdOption); export.Add(exportFormatOption); export.Add(exportOutputOption); export.SetAction((parseResult, _) => { var keyId = parseResult.GetValue(exportKeyIdOption); var format = parseResult.GetValue(exportFormatOption); Console.WriteLine($"Exporting public key {keyId} as {format}..."); Console.WriteLine("-----BEGIN PUBLIC KEY-----"); Console.WriteLine("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE..."); Console.WriteLine("-----END PUBLIC KEY-----"); return Task.FromResult(0); }); keys.Add(generate); keys.Add(list); keys.Add(import); keys.Add(export); return keys; } /// /// Build the 'crypto encrypt' command. /// private static Command BuildEncryptCommand(Option verboseOption) { var encrypt = new Command("encrypt", "Encrypt data with a key or certificate."); var inputOption = new Option("--input", "-i") { Description = "Input file to encrypt", Required = true }; var outputOption = new Option("--output", "-o") { Description = "Output file for encrypted data", Required = true }; var keyOption = new Option("--key", "-k") { Description = "Key ID or path" }; var certOption = new Option("--cert", "-c") { Description = "Certificate path (for asymmetric)" }; var algorithmOption = new Option("--algorithm", "-a") { Description = "Encryption algorithm: aes-256-gcm, chacha20-poly1305" }; algorithmOption.SetDefaultValue("aes-256-gcm"); encrypt.Add(inputOption); encrypt.Add(outputOption); encrypt.Add(keyOption); encrypt.Add(certOption); encrypt.Add(algorithmOption); encrypt.SetAction((parseResult, _) => { var input = parseResult.GetValue(inputOption); var output = parseResult.GetValue(outputOption); var algorithm = parseResult.GetValue(algorithmOption); Console.WriteLine($"Encrypting: {input}"); Console.WriteLine($"Algorithm: {algorithm}"); Console.WriteLine($"Output: {output}"); Console.WriteLine("Encryption successful"); return Task.FromResult(0); }); return encrypt; } /// /// Build the 'crypto decrypt' command. /// private static Command BuildDecryptCommand(Option verboseOption) { var decrypt = new Command("decrypt", "Decrypt data with a key or certificate."); var inputOption = new Option("--input", "-i") { Description = "Encrypted file to decrypt", Required = true }; var outputOption = new Option("--output", "-o") { Description = "Output file for decrypted data", Required = true }; var keyOption = new Option("--key", "-k") { Description = "Key ID or path" }; var certOption = new Option("--cert", "-c") { Description = "Private key path (for asymmetric)" }; decrypt.Add(inputOption); decrypt.Add(outputOption); decrypt.Add(keyOption); decrypt.Add(certOption); decrypt.SetAction((parseResult, _) => { var input = parseResult.GetValue(inputOption); var output = parseResult.GetValue(outputOption); Console.WriteLine($"Decrypting: {input}"); Console.WriteLine($"Output: {output}"); Console.WriteLine("Decryption successful"); return Task.FromResult(0); }); return decrypt; } /// /// Build the 'crypto hash' command. /// private static Command BuildHashCommand(Option verboseOption) { var hash = new Command("hash", "Compute cryptographic hash of files."); var inputOption = new Option("--input", "-i") { Description = "File to hash", Required = true }; var algorithmOption = new Option("--algorithm", "-a") { Description = "Hash algorithm: sha256, sha384, sha512, sha3-256" }; algorithmOption.SetDefaultValue("sha256"); var formatOption = new Option("--format", "-f") { Description = "Output format: hex, base64, sri" }; formatOption.SetDefaultValue("hex"); hash.Add(inputOption); hash.Add(algorithmOption); hash.Add(formatOption); hash.SetAction((parseResult, _) => { var input = parseResult.GetValue(inputOption); var algorithm = parseResult.GetValue(algorithmOption); var format = parseResult.GetValue(formatOption); Console.WriteLine($"Hashing: {input}"); Console.WriteLine($"Algorithm: {algorithm}"); Console.WriteLine($"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); return Task.FromResult(0); }); return hash; } #endregion }