Files
git.stella-ops.org/src/Cli/StellaOps.Cli/Commands/CryptoCommandGroup.cs
2026-02-01 21:37:40 +02:00

771 lines
30 KiB
C#

// 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;
/// <summary>
/// CLI commands for cryptographic operations with regional compliance support.
/// Supports GOST (Russia), eIDAS (EU), SM (China), and international crypto.
/// </summary>
internal static class CryptoCommandGroup
{
/// <summary>
/// Build the crypto command group with sign/verify/profiles/plugins subcommands.
/// </summary>
public static Command BuildCryptoCommand(
IServiceProvider serviceProvider,
Option<bool> 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<bool> verboseOption,
CancellationToken cancellationToken)
{
var command = new Command("sign", "Sign artifacts using configured crypto provider");
var inputOption = new Option<string>("--input")
{
Description = "Path to file or artifact to sign",
Required = true
};
command.Add(inputOption);
var outputOption = new Option<string?>("--output")
{
Description = "Output path for signature (defaults to <input>.sig)"
};
command.Add(outputOption);
var providerOption = new Option<string?>("--provider")
{
Description = "Override crypto provider (e.g., gost-cryptopro, eidas-tsp, sm-remote)"
};
command.Add(providerOption);
var keyIdOption = new Option<string?>("--key-id")
{
Description = "Key identifier for signing operation"
};
command.Add(keyIdOption);
var formatOption = new Option<string?>("--format")
{
Description = "Signature format: dsse, jws, raw (default: dsse)"
};
command.Add(formatOption);
var detachedOption = new Option<bool>("--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<bool> verboseOption,
CancellationToken cancellationToken)
{
var command = new Command("verify", "Verify signatures using configured crypto provider");
var inputOption = new Option<string>("--input")
{
Description = "Path to file or artifact to verify",
Required = true
};
command.Add(inputOption);
var signatureOption = new Option<string?>("--signature")
{
Description = "Path to signature file (defaults to <input>.sig)"
};
command.Add(signatureOption);
var providerOption = new Option<string?>("--provider")
{
Description = "Override crypto provider for verification"
};
command.Add(providerOption);
var trustPolicyOption = new Option<string?>("--trust-policy")
{
Description = "Path to trust policy YAML file"
};
command.Add(trustPolicyOption);
var formatOption = new Option<string?>("--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<bool> 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;
}
/// <summary>
/// Build the 'crypto profiles list' command.
/// Sprint: SPRINT_20260117_012_CLI_regional_crypto (RCR-001)
/// </summary>
private static Command BuildProfilesListCommand(
IServiceProvider serviceProvider,
Option<bool> verboseOption,
CancellationToken cancellationToken)
{
var command = new Command("list", "List available crypto profiles");
var formatOption = new Option<string>("--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;
}
/// <summary>
/// Build the 'crypto profiles select' command.
/// Sprint: SPRINT_20260117_012_CLI_regional_crypto (RCR-001)
/// </summary>
private static Command BuildProfilesSelectCommand(
IServiceProvider serviceProvider,
Option<bool> verboseOption,
CancellationToken cancellationToken)
{
var command = new Command("select", "Select active crypto profile");
var profileArg = new Argument<string>("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<bool> verboseOption,
CancellationToken cancellationToken)
{
var command = new Command("show", "Show current active profile and its capabilities");
var showDetailsOption = new Option<bool>("--details")
{
Description = "Show detailed provider capabilities"
};
command.Add(showDetailsOption);
var providerFilterOption = new Option<string?>("--provider")
{
Description = "Filter by provider name"
};
command.Add(providerFilterOption);
var testOption = new Option<bool>("--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;
}
/// <summary>
/// Build the 'crypto plugins' command group.
/// Sprint: SPRINT_20260117_012_CLI_regional_crypto (RCR-002)
/// </summary>
private static Command BuildPluginsCommand(
IServiceProvider serviceProvider,
Option<bool> verboseOption,
CancellationToken cancellationToken)
{
var command = new Command("plugins", "Manage crypto plugins");
command.Add(BuildPluginsStatusCommand(serviceProvider, verboseOption, cancellationToken));
return command;
}
/// <summary>
/// Build the 'crypto plugins status' command.
/// Sprint: SPRINT_20260117_012_CLI_regional_crypto (RCR-002)
/// </summary>
private static Command BuildPluginsStatusCommand(
IServiceProvider serviceProvider,
Option<bool> verboseOption,
CancellationToken cancellationToken)
{
var command = new Command("status", "Show status of crypto plugins");
var formatOption = new Option<string>("--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<int> 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<int> 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<int> 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<CryptoProfile> 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<CryptoPluginStatus> 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)
/// <summary>
/// Build the 'crypto keys' command group.
/// Moved from stella sigstore, stella cosign
/// </summary>
private static Command BuildKeysCommand(Option<bool> 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<string>("--algorithm", "-a") { Description = "Key algorithm: rsa, ecdsa, ed25519" };
algOption.SetDefaultValue("ecdsa");
var sizeOption = new Option<int?>("--size", "-s") { Description = "Key size (for RSA)" };
var outputOption = new Option<string>("--output", "-o") { Description = "Output path prefix", Required = true };
var passwordOption = new Option<bool>("--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<string>("--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<string>("--source", "-s") { Description = "Key source: file, sigstore, cosign", Required = true };
var importPathOption = new Option<string?>("--path", "-p") { Description = "Path to key file (for file import)" };
var keyIdOption = new Option<string>("--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<string>("--key-id", "-k") { Description = "Key ID to export", Required = true };
var exportFormatOption = new Option<string>("--format", "-f") { Description = "Export format: pem, jwk, ssh" };
exportFormatOption.SetDefaultValue("pem");
var exportOutputOption = new Option<string?>("--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;
}
/// <summary>
/// Build the 'crypto encrypt' command.
/// </summary>
private static Command BuildEncryptCommand(Option<bool> verboseOption)
{
var encrypt = new Command("encrypt", "Encrypt data with a key or certificate.");
var inputOption = new Option<string>("--input", "-i") { Description = "Input file to encrypt", Required = true };
var outputOption = new Option<string>("--output", "-o") { Description = "Output file for encrypted data", Required = true };
var keyOption = new Option<string?>("--key", "-k") { Description = "Key ID or path" };
var certOption = new Option<string?>("--cert", "-c") { Description = "Certificate path (for asymmetric)" };
var algorithmOption = new Option<string>("--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;
}
/// <summary>
/// Build the 'crypto decrypt' command.
/// </summary>
private static Command BuildDecryptCommand(Option<bool> verboseOption)
{
var decrypt = new Command("decrypt", "Decrypt data with a key or certificate.");
var inputOption = new Option<string>("--input", "-i") { Description = "Encrypted file to decrypt", Required = true };
var outputOption = new Option<string>("--output", "-o") { Description = "Output file for decrypted data", Required = true };
var keyOption = new Option<string?>("--key", "-k") { Description = "Key ID or path" };
var certOption = new Option<string?>("--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;
}
/// <summary>
/// Build the 'crypto hash' command.
/// </summary>
private static Command BuildHashCommand(Option<bool> verboseOption)
{
var hash = new Command("hash", "Compute cryptographic hash of files.");
var inputOption = new Option<string>("--input", "-i") { Description = "File to hash", Required = true };
var algorithmOption = new Option<string>("--algorithm", "-a") { Description = "Hash algorithm: sha256, sha384, sha512, sha3-256" };
algorithmOption.SetDefaultValue("sha256");
var formatOption = new Option<string>("--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
}