1using System.CommandLine;
 
    2using System.Diagnostics.Metrics;
 
    3using System.Security.AccessControl;
 
    4using System.Security.Cryptography.X509Certificates;
 
    7using System.Text.Json.Serialization;
 
    9using Microsoft.Extensions.Logging;
 
   10using Microsoft.Extensions.Logging.Abstractions;
 
   28        protected ILogger 
Logger { 
get; 
set; }
 
   48            Logger = logger ?? NullLogger.Instance;
 
 
   54        protected Argument<string> 
BundlePath { 
get; } = 
new Argument<string>(
"bundle", 
"Bundle path or directory containing the bundle\n" +
 
   55            "if the bundle name is not specified, a default name will be used");
 
   70                Argument<string[]> filesArg = 
new Argument<string[]>(
"files", description: 
"Files to add to the bundle, Must be inside the bundle root path\n" +
 
   71                    "if not specified, all files in the bundle root path will be added", parse: x =>
 
   73                        List<string> result = [];
 
   74                        foreach (var file 
in x.Tokens.Select(t => t.Value))
 
   76                            if (
string.IsNullOrEmpty(file)) 
continue;
 
   78                            result.Add(Path.GetFullPath(file));
 
   81                        return result.ToArray();
 
   84                    Arity = ArgumentArity.ZeroOrMore,
 
   87                Option<bool> replaceOpt = 
new Option<bool>(
"--replace", 
"Replace existing entries");
 
   88                replaceOpt.AddAlias(
"-r");
 
   90                Option<bool> recursiveOpt = 
new Option<bool>(
"--recursive", 
"Add all files within the bundle root path recursively");
 
   91                recursiveOpt.AddAlias(
"-R");
 
   93                Option<bool> continueOpt = 
new Option<bool>(
"--continue", 
"Continue adding files if an error occurs");
 
   94                continueOpt.AddAlias(
"-c");
 
   96                Option<bool> forceOpt = 
new Option<bool>(
"--force", 
"Add files even if the bundle is signed");
 
   98                Command command = 
new Command(
"add", 
"Create new bundle or update an existing one")
 
  107                command.SetHandler((bundlePath, files, replace, recursive, continueOnError, force) =>
 
  110                    Utilities.RunInStatusContext(
"[yellow]Preparing[/]", ctx => 
RunAdd(ctx, files, replace, recursive, continueOnError, force));
 
  111                }, 
BundlePath, filesArg, replaceOpt, recursiveOpt, continueOpt, forceOpt);
 
 
  124                Command command = 
new Command(
"info", 
"Show bundle information")
 
  129                command.SetHandler((bundlePath) =>
 
  132                    Utilities.RunInStatusContext(
"[yellow]Preparing[/]", ctx => 
RunInfo(ctx));
 
 
  146                Option<string> pfxOpt = 
new Option<string>(
"--pfx", 
"PFX File contains certificate and private key");
 
  147                Option<string> pfxPassOpt = 
new Option<string>(
"--pfx-password", 
"PFX File password");
 
  148                Option<bool> pfxNoPassOpt = 
new Option<bool>(
"--no-password", 
"Ignore PFX File password prompt");
 
  150                Option<bool> selfSignOpt = 
new Option<bool>(
"--self-sign", 
"Sign with self-signed certificate");
 
  151                selfSignOpt.AddAlias(
"-s");
 
  153                Option<bool> skipVerifyOpt = 
new Option<bool>(
"--skip-verification", 
"Skip verification of the certificate");
 
  155                Command command = 
new Command(
"sign", 
"Sign bundle with certificate")
 
  165                command.SetHandler((bundlePath, pfxFilePath, pfxFilePassword, pfxNoPasswordPrompt, selfSign, skipVerify) =>
 
  169                    X509Certificate2Collection collection;
 
  170                    X509Certificate2Collection certs;
 
  176                            AnsiConsole.MarkupLine(
"[red]Self-Signing feature is disabled[/]");
 
  183                            AnsiConsole.MarkupLine(
"[red]Self-Signing Root CA not found[/]");
 
  187                        string? selectedCert = 
null;
 
  190                            selectedCert = AnsiConsole.Prompt<
string>(
 
  191                            new SelectionPrompt<string>()
 
  193                                .Title(
"Select Self-Signing Certificate")
 
  194                                .MoreChoicesText(
"[grey](Move up and down to see more certificates)[/]")
 
  196                                .AddChoices(
"Issue New Certificate"));
 
  199                        if (
string.IsNullOrEmpty(selectedCert) || selectedCert == 
"Issue New Certificate")
 
  201                            var subject = CertificateUtilities.GetSubjectFromUser();
 
  202                            var issuedCert = CertificateUtilities.IssueCertificate(subject.ToString(), rootCA);
 
  206                            certs = 
new X509Certificate2Collection(issuedCert);
 
  215                        certs = CertificateUtilities.GetCertificates(pfxFilePath, pfxFilePassword, pfxNoPasswordPrompt);
 
  218                    if (certs.Count == 0)
 
  220                        AnsiConsole.MarkupLine(
"[red]No certificates found![/]");
 
  223                    else if (certs.Count == 1)
 
  229                        Dictionary<string, X509Certificate2> mapping = [];
 
  230                        foreach (X509Certificate2 cert 
in certs)
 
  232                            mapping[$
"{cert.GetNameInfo(X509NameType.SimpleName, false)},{cert.GetNameInfo(X509NameType.SimpleName, true)},{cert.Thumbprint}"] = cert;
 
  235                        List<string> selection = AnsiConsole.Prompt(
 
  236                            new MultiSelectionPrompt<string>()
 
  238                                .Title(
"Select Signing Certificates")
 
  239                                .MoreChoicesText(
"[grey](Move up and down to see more certificates)[/]")
 
  240                                .InstructionsText(
"[grey](Press [blue]<space>[/] to toggle a certificate, [green]<enter>[/] to accept)[/]")
 
  241                                .AddChoices(mapping.Keys));
 
  243                        collection = 
new(selection.Select(x => mapping[x]).ToArray());
 
  246                    Utilities.RunInStatusContext(
"[yellow]Preparing[/]", ctx => 
RunSign(ctx, collection, skipVerify));
 
  247                }, 
BundlePath, pfxOpt, pfxPassOpt, pfxNoPassOpt, selfSignOpt, skipVerifyOpt);
 
 
  260                var ignoreTimeOpt = 
new Option<bool>(
"--ignore-time", 
"Ignore time validation");
 
  261                ignoreTimeOpt.AddAlias(
"-i");
 
  263                Command command = 
new Command(
"verify", 
"Verify bundle")
 
  269                command.SetHandler((bundlePath, ignoreTime) =>
 
  272                    Utilities.RunInStatusContext(
"[yellow]Preparing[/]", ctx => 
RunVerify(ctx, ignoreTime));
 
 
  286                var forceOpt = 
new Option<bool>(
"--force", 
"Generate new self-signed root CA even if one already exists");
 
  287                forceOpt.AddAlias(
"-f");
 
  289                var cnOption = 
new Option<string>(
 
  290                    aliases: [
"--commonName", 
"-cn"],
 
  291                    description: 
"Common Name for the certificate (e.g., example.com)\n" +
 
  292                                 "If not specified, the user will be prompted for input.");
 
  294                var emailOption = 
new Option<string>(
 
  295                    aliases: [
"--email", 
"-e"],
 
  296                    description: 
"Email address (e.g., support@example.com)");
 
  298                var orgOption = 
new Option<string>(
 
  299                    aliases: [
"--organization", 
"-o"],
 
  300                    description: 
"Organization name (e.g., Example Inc.)");
 
  302                var ouOption = 
new Option<string>(
 
  303                    aliases: [
"--organizationalUnit", 
"-ou"],
 
  304                    description: 
"Organizational Unit (e.g., IT Department)");
 
  306                var locOption = 
new Option<string>(
 
  307                    aliases: [
"--locality", 
"-l"],
 
  308                    description: 
"Locality (e.g., New York)");
 
  310                var stateOption = 
new Option<string>(
 
  311                    aliases: [
"--state", 
"-st"],
 
  312                    description: 
"State or Province (e.g., NY)");
 
  314                var countryOption = 
new Option<string>(
 
  315                    aliases: [
"--country", 
"-c"],
 
  316                    description: 
"Country (e.g., US)");
 
  318                var command = 
new Command(
"self-sign", 
"Generate self-signed root CA")
 
  330                command.SetHandler(
RunSelfSign, forceOpt, cnOption, emailOption, orgOption, ouOption, locOption, stateOption, countryOption);
 
 
  343                var caPathArg = 
new Argument<string>(
"path", 
"Path to the certificate file in PEM or DER format")
 
  345                    Arity = ArgumentArity.ExactlyOne,
 
  348                var interOpt = 
new Option<bool>(
"--intermediate", 
"Run command for Intermediate CA");
 
  349                interOpt.AddAlias(
"-i");
 
  351                Command addCmd = 
new Command(
"add", 
"Add trusted root CA or intermediate CA certificate")
 
  357                addCmd.SetHandler((path, intermediate) =>
 
  359                    if (!File.Exists(path))
 
  361                        AnsiConsole.MarkupLine($
"[red]Certificate file not found: {path}[/]");
 
  365                    var certificate = CertificateUtilities.Import(path);
 
  366                    CertificateUtilities.DisplayCertificate(certificate);
 
  368                    var modifier = intermediate ? 
"Intermediate" : 
"Trusted Root";
 
  369                    var store = intermediate ? CertificateStore.IntermediateCA : 
CertificateStore.TrustedRootCA;
 
  372                    AnsiConsole.MarkupLine($
"[green] {modifier} CA certificate added with ID: {id}[/]");
 
  373                }, caPathArg, interOpt);
 
  375                var verboseOpt = 
new Option<bool>(
"--verbose", 
"Show detailed information about the certificate");
 
  376                verboseOpt.AddAlias(
"-v");
 
  378                var listCmd = 
new Command(
"list", 
"List trusted root CA and intermediate CA certificates")
 
  384                listCmd.SetHandler((intermediate, verbose) =>
 
  386                    string modifier = intermediate ? 
"Intermediate" : 
"Trusted Root";
 
  387                    var store = intermediate ? CertificateStore.IntermediateCA : 
CertificateStore.TrustedRootCA;
 
  388                    var target = intermediate ? Configuration.IntermediateCA : 
Configuration.TrustedRootCA;
 
  390                    X509Certificate2Collection certificates = [];
 
  391                    AnsiConsole.WriteLine($
"{modifier} CA certificates:");
 
  392                    foreach (var cert 
in target)
 
  394                        var certificate = 
Configuration.LoadCertificate(store, cert.Key);
 
  398                        AnsiConsole.MarkupLine($
"[{(isProtected ? Color.Green : Color.White)}]   {cert.Key}{(isProtected ? " (Protected)
" : "")}[/]");
 
  400                        certificates.Add(certificate);
 
  405                        AnsiConsole.WriteLine();
 
  406                        CertificateUtilities.DisplayCertificate(certificates.ToArray());
 
  409                }, interOpt, verboseOpt);
 
  411                var idArg = 
new Argument<string>(
"ID", 
"ID of the certificate")
 
  413                    Arity = ArgumentArity.ExactlyOne,
 
  416                var removeCmd = 
new Command(
"remove", 
"Remove trusted root CA or intermediate CA certificate")
 
  422                removeCmd.SetHandler((
id, intermediate) =>
 
  424                    var modifier = intermediate ? 
"Intermediate" : 
"Trusted Root";
 
  425                    var store = intermediate ? CertificateStore.IntermediateCA : 
CertificateStore.TrustedRootCA;
 
  429                        AnsiConsole.MarkupLine($
"[red]This ID is protected and cannot be modified[/]");
 
  435                        AnsiConsole.MarkupLine($
"[green]{modifier} CA certificate removed with ID: {id}[/]");
 
  439                        AnsiConsole.MarkupLine($
"[red]{modifier} CA certificate with ID: {id} not found![/]");
 
  443                Command command = 
new Command(
"trust", 
"Manage trusted root CAs and intermediate CAs");
 
  447                    command.AddCommand(addCmd);
 
  448                    command.AddCommand(listCmd);
 
  449                    command.AddCommand(removeCmd);
 
  453                    command.SetHandler(() =>
 
  455                        AnsiConsole.MarkupLine(
"[red]Custom trust store feature is disabled[/]");
 
 
  471                var keyArg = 
new Argument<string>(
"key", 
"Key to set or get\n" +
 
  472                    "if not specified, will list all keys")
 
  474                    Arity = ArgumentArity.ZeroOrOne,
 
  477                var valueArg = 
new Argument<string>(
"value", 
"Value to set\n" +
 
  478                    "if not specified, will get the value of the key")
 
  480                    Arity = ArgumentArity.ZeroOrOne,
 
  483                var forceOpt = 
new Option<bool>(
"--force", 
"Set value even if it is not existing");
 
  484                forceOpt.AddAlias(
"-f");
 
  486                var command = 
new Command(
"config", 
"Get or set configuration values")
 
  493                command.SetHandler((key, value, force) =>
 
  495                    if (
string.IsNullOrEmpty(value))
 
  497                        var items = 
string.IsNullOrEmpty(key) ? Configuration.Settings : 
Configuration.Settings.Where(x => x.Key.StartsWith(key));
 
  499                        foreach (var item 
in items)
 
  501                            AnsiConsole.WriteLine($
"{item.Key} = {item.Value}");
 
  508                            AnsiConsole.MarkupLine($
"[red]Invalid key: {key}[/]");
 
  515                            bValue = Utilities.ParseToBool(value);
 
  519                            AnsiConsole.MarkupLine($
"[red]Invalid value: {value}[/]");
 
  524                        AnsiConsole.MarkupLine($
"[green]{key} set to {Configuration.Settings[key]}[/]");
 
  526                }, keyArg, valueArg, forceOpt);
 
 
  543        public virtual void RunSelfSign(
bool force, 
string? commonName, 
string? email, 
string? organization, 
string? organizationalUnit, 
string? locality, 
string? state, 
string? country)
 
  547                AnsiConsole.MarkupLine(
"[red]Self-Signing feature is disabled[/]");
 
  551            Logger.LogInformation(
"Running self-sign command");
 
  555                Logger.LogWarning(
"Root CA already exists");
 
  556                AnsiConsole.MarkupLine(
"[red]Root CA already exists![/]");
 
  562            if (
string.IsNullOrEmpty(commonName))
 
  564                Logger.LogDebug(
"Getting subject name from user");
 
  565                subject = CertificateUtilities.GetSubjectFromUser().ToString();
 
  571                                                 organization: organization,
 
  572                                                 organizationalUnit: organizationalUnit,
 
  578            Logger.LogInformation(
"Creating self-signed root CA certificate with subject: {subject}", subject);
 
  579            var rootCA = CertificateUtilities.CreateSelfSignedCACertificate(subject);
 
  580            Logger.LogDebug(
"Root CA certificate issued with subject: {subject}", rootCA.Subject);
 
  582            Logger.LogDebug(
"Exporting root CA certificate to configuration");
 
  583            Configuration.SelfSignedRootCA = rootCA.Export(X509ContentType.Pfx);
 
  585            Logger.LogDebug(
"Clearing issued certificates");
 
  588            CertificateUtilities.DisplayCertificate(rootCA);
 
  590            Logger.LogInformation(
"Root CA created successfully");
 
  591            AnsiConsole.MarkupLine($
"[green]Root CA created successfully![/]");
 
 
  604                return CertificateUtilities.ImportPFX(
Configuration.SelfSignedRootCA).Single();
 
 
 
Represents a bundle that holds file hashes and signatures.
Represents the subject of a certificate.
override string ToString()
Generates a comma-delimited string representation of the certificate subject.
Represents the configuration for the EasySign command provider.
Provides command definitions and handlers for the EasySign command line interface.
TConfiguration Configuration
Gets the application configurations.
Command SelfSign
Gets the command for generating a self-signed root CA certificate.
Command Info
Gets the command for Showing bundle information.
virtual void RunAdd(StatusContext statusContext, string[] files, bool replace, bool recursive, bool continueOnError, bool force)
Runs the add command.
Command?? Trust
Gets the command for managing trusted root CAs and intermediate CAs.
Command Add
Gets the command for creating a new bundle or updating an existing one.
CommandProvider(TConfiguration? configuration, ILogger? logger)
Initializes a new instance of the CommandProvider<TBundle, TConfiguration> class.
Command Verify
Gets the command for verifying bundle.
virtual void RunSelfSign(bool force, string? commonName, string? email, string? organization, string? organizationalUnit, string? locality, string? state, string? country)
Runs the self-sign command to create a self-signed root CA certificate.
void InitializeBundle(string bundlePath)
Initializes the bundle.
Command Config
Gets the command for managing configuration settings.
virtual void RunInfo(StatusContext statusContext)
Runs the info command.
X509Certificate2? GetSelfSigningRootCA()
Gets the self-signed root CA.
ILogger Logger
Gets or sets the logger to use for logging.
Argument< string > BundlePath
Gets the common argument for the bundle path.
virtual void RunSign(StatusContext statusContext, X509Certificate2Collection certificates, bool skipVerify)
Runs the sign command.
virtual bool RunVerify(StatusContext statusContext, bool ignoreTime)
Runs the verify command.
Command Sign
Gets the command for signing bundle with one or more certificate.
RootCommand GetRootCommand()
Gets the root command for the command line interface.
CertificateStore
Enumeration of certificate stores in the CommandProviderConfiguration.