Skip to content

Commit 19b9f4a

Browse files
committed
Overhaul CommandProvider and add trust command
1 parent 95f5441 commit 19b9f4a

File tree

8 files changed

+525
-145
lines changed

8 files changed

+525
-145
lines changed

src/EasySign.Cli/BundleCommandProvider.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
using System.CommandLine;
2+
using System.Text.Json.Serialization;
23

34
using Microsoft.Extensions.Logging;
45

56
using SAPTeam.EasySign.CommandLine;
67

78
namespace SAPTeam.EasySign.Cli
89
{
9-
internal class BundleCommandProvider : CommandProvider<Bundle>
10+
internal class BundleCommandProvider : CommandProvider<Bundle, CommandProviderConfiguration>
1011
{
1112
private readonly ILogger _bundleLogger;
1213

13-
public BundleCommandProvider(string appDirectory, ILogger logger, ILogger bundleLogger) : base(appDirectory, logger)
14+
public BundleCommandProvider(CommandProviderConfiguration configuration, ILogger logger, ILogger bundleLogger) : base(configuration, logger)
1415
{
1516
_bundleLogger = bundleLogger;
1617
}
@@ -30,9 +31,17 @@ public override RootCommand GetRootCommand()
3031
Sign,
3132
Verify,
3233
SelfSign,
34+
Trust,
3335
};
3436

3537
return root;
3638
}
3739
}
40+
41+
[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Metadata, WriteIndented = true, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingDefault)]
42+
[JsonSerializable(typeof(CommandProviderConfiguration))]
43+
internal partial class SourceGenerationConfigurationContext : JsonSerializerContext
44+
{
45+
46+
}
3847
}

src/EasySign.Cli/Program.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
using System.CommandLine;
2+
using System.Text.Json.Serialization;
3+
using System.Text.Json;
4+
using System.Text;
25

36
using Serilog;
47
using Serilog.Extensions.Logging;
8+
using SAPTeam.EasySign.CommandLine;
59

610
namespace SAPTeam.EasySign.Cli
711
{
812
internal class Program
913
{
1014
public static string AppDirectory => AppDomain.CurrentDomain.BaseDirectory;
1115

16+
public static string ConfigPath => Path.Combine(AppDirectory, "config.json");
17+
1218
private static int Main(string[] args)
1319
{
1420
Log.Logger = new LoggerConfiguration()
@@ -30,15 +36,30 @@ private static int Main(string[] args)
3036
Microsoft.Extensions.Logging.ILogger commandProviderLogger = new SerilogLoggerFactory(Log.Logger.ForContext("Context", "CommandProvider"))
3137
.CreateLogger("CommandProvider");
3238

33-
int exitCode;
34-
using (var cp = new BundleCommandProvider(AppDirectory, commandProviderLogger, bundleLogger))
39+
CommandProviderConfiguration config;
40+
if (File.Exists(ConfigPath))
41+
{
42+
using FileStream fs = File.OpenRead(ConfigPath);
43+
config = JsonSerializer.Deserialize(fs, typeof(CommandProviderConfiguration), SourceGenerationConfigurationContext.Default) as CommandProviderConfiguration ?? new CommandProviderConfiguration();
44+
}
45+
else
3546
{
36-
RootCommand root = cp.GetRootCommand();
37-
exitCode = root.Invoke(args);
47+
config = new CommandProviderConfiguration();
3848
}
39-
49+
50+
var cp = new BundleCommandProvider(config, commandProviderLogger, bundleLogger);
51+
52+
RootCommand root = cp.GetRootCommand();
53+
int exitCode = root.Invoke(args);
54+
4055
appLogger.Information("Shutting down EasySign CLI at {DateTime} with exit code {ExitCode}", DateTime.Now, exitCode);
4156

57+
string data = JsonSerializer.Serialize(config, config.GetType(), SourceGenerationConfigurationContext.Default);
58+
using (FileStream fs = File.OpenWrite(ConfigPath))
59+
{
60+
fs.Write(Encoding.UTF8.GetBytes(data));
61+
}
62+
4263
Log.CloseAndFlush();
4364
return exitCode;
4465
}

src/EasySign.CommandLine/BundleWorker.cs

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
using Spectre.Console;
66

7+
using static System.Net.Mime.MediaTypeNames;
8+
79
namespace SAPTeam.EasySign.CommandLine
810
{
9-
public abstract partial class CommandProvider<TBundle>
11+
public abstract partial class CommandProvider<TBundle, TConfiguration>
1012
{
1113
/// <summary>
1214
/// Gets or sets the bundle.
@@ -273,7 +275,8 @@ protected virtual void RunInfo(StatusContext statusContext)
273275
/// </summary>
274276
/// <param name="statusContext">The status context for interacting with <see cref="AnsiConsole.Status"/>.</param>
275277
/// <param name="certificates">The certificates.</param>
276-
protected virtual void RunSign(StatusContext statusContext, X509Certificate2Collection certificates)
278+
/// <param name="skipVerify">A value indicating whether to skip certificate verification.</param>
279+
protected virtual void RunSign(StatusContext statusContext, X509Certificate2Collection certificates, bool skipVerify)
277280
{
278281
Logger.LogInformation("Running sign command");
279282

@@ -305,14 +308,17 @@ protected virtual void RunSign(StatusContext statusContext, X509Certificate2Coll
305308

306309
CertificateUtilities.DisplayCertificate(cert);
307310

308-
Logger.LogDebug("Verifying certificate {cert}", cert);
309-
statusContext.Status("[yellow]Verifying Certificate[/]");
310-
311-
bool verifyCert = VerifyCertificate(cert, false);
312-
if (!verifyCert)
311+
if (!skipVerify)
313312
{
314-
Logger.LogWarning("Skipping signing with {cert}", cert);
315-
continue;
313+
Logger.LogDebug("Verifying certificate {cert}", cert);
314+
statusContext.Status("[yellow]Verifying Certificate[/]");
315+
316+
bool verifyCert = VerifyCertificate(cert, false);
317+
if (!verifyCert)
318+
{
319+
Logger.LogWarning("Skipping signing with {cert}", cert);
320+
continue;
321+
}
316322
}
317323

318324
Logger.LogDebug("Acquiring RSA private key for {cert}", cert);
@@ -526,6 +532,9 @@ protected bool VerifyCertificate(X509Certificate2 certificate, bool ignoreTime)
526532
throw new ApplicationException("Bundle is not initialized");
527533
}
528534

535+
List<bool> verificationResults = [];
536+
List<X509ChainStatus[]> verificationStatuses = [];
537+
529538
X509Certificate2? rootCA;
530539
if ((rootCA = GetSelfSigningRootCA()) != null)
531540
{
@@ -537,9 +546,12 @@ protected bool VerifyCertificate(X509Certificate2 certificate, bool ignoreTime)
537546
selfSignPolicy.VerificationFlags |= X509VerificationFlags.IgnoreNotTimeValid;
538547
selfSignPolicy.RevocationMode = X509RevocationMode.NoCheck;
539548

540-
bool selfSignVerification = Bundle.VerifyCertificate(certificate, out _, policy: selfSignPolicy);
549+
bool selfSignVerification = Bundle.VerifyCertificate(certificate, out X509ChainStatus[] selfSignChainStatuses, policy: selfSignPolicy);
541550
Logger.LogInformation("Certificate verification with self-signing root CA for {cert}: {result}", certificate, selfSignVerification);
542551

552+
verificationResults.Add(selfSignVerification);
553+
verificationStatuses.Add(selfSignChainStatuses);
554+
543555
if (selfSignVerification)
544556
{
545557
AnsiConsole.MarkupLine($"[{Color.Green}] Certificate Verification with Self-Signing Root CA Successful[/]");
@@ -548,29 +560,48 @@ protected bool VerifyCertificate(X509Certificate2 certificate, bool ignoreTime)
548560
}
549561

550562
X509ChainPolicy policy = new();
563+
policy.ExtraStore.AddRange(Configuration.LoadCertificates(CertificateStore.IntermediateCA));
551564

552565
if (ignoreTime)
553566
{
554567
policy.VerificationFlags |= X509VerificationFlags.IgnoreCtlNotTimeValid;
555568
}
556569

557570
Logger.LogDebug("Verifying certificate {cert} with system trust store", certificate);
558-
bool defaultVerification = Bundle.VerifyCertificate(certificate, out X509ChainStatus[] statuses, policy);
559-
571+
bool defaultVerification = Bundle.VerifyCertificate(certificate, out X509ChainStatus[] defaultChainStatuses, policy);
560572
Logger.LogInformation("Certificate verification with system trust store for {cert}: {result}", certificate, defaultVerification);
561573

562-
if (!defaultVerification)
574+
verificationResults.Add(defaultVerification);
575+
verificationStatuses.Add(defaultChainStatuses);
576+
577+
if (!verificationResults.Any(x => x) && Configuration.TrustedRootCA.Count > 0)
563578
{
564-
Utilities.EnumerateStatuses(statuses);
579+
policy.TrustMode = X509ChainTrustMode.CustomRootTrust;
580+
policy.CustomTrustStore.AddRange(Configuration.LoadCertificates(CertificateStore.TrustedRootCA));
581+
582+
Logger.LogDebug("Verifying certificate {cert} with custom trust store", certificate);
583+
bool customVerification = Bundle.VerifyCertificate(certificate, out X509ChainStatus[] customChainStatuses, policy);
584+
Logger.LogInformation("Certificate verification with custom trust store for {cert}: {result}", certificate, defaultVerification);
585+
586+
verificationResults.Add(customVerification);
587+
verificationStatuses.Add(customChainStatuses);
565588
}
566-
else
589+
590+
if (!verificationResults.Any(x => x))
567591
{
568-
AnsiConsole.MarkupLine($"[green] Certificate Verification Successful[/]");
569-
return true;
592+
var statuses = verificationStatuses.Aggregate((prev, next) =>
593+
{
594+
return prev.Intersect(next).ToArray();
595+
});
596+
597+
Utilities.EnumerateStatuses(statuses);
598+
599+
AnsiConsole.MarkupLine($"[red] Certificate Verification Failed[/]");
600+
return false;
570601
}
571602

572-
AnsiConsole.MarkupLine($"[red] Certificate Verification Failed[/]");
573-
return false;
603+
AnsiConsole.MarkupLine($"[green] Certificate Verification Successful[/]");
604+
return true;
574605
}
575606
}
576607
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace SAPTeam.EasySign.CommandLine
8+
{
9+
/// <summary>
10+
/// Enumeration of certificate stores in the <see cref="CommandProviderConfiguration"/>.
11+
/// </summary>
12+
public enum CertificateStore
13+
{
14+
/// <summary>
15+
/// The trusted root CA store.
16+
/// </summary>
17+
TrustedRootCA,
18+
19+
/// <summary>
20+
/// The intermediate CA store.
21+
/// </summary>
22+
IntermediateCA,
23+
24+
/// <summary>
25+
/// The self-signed certificate store.
26+
/// </summary>
27+
IssuedCertificates,
28+
}
29+
}

src/EasySign.CommandLine/CertificateUtilities.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,14 @@ public static X509Certificate2Collection ImportPFX(byte[] data, string? password
182182

183183
return collection;
184184
}
185-
186185

187186
/// <summary>
188-
/// Prompts the user for certificate subject information and generates a standardized subject name.
187+
/// Prompts the user for certificate subject information and generates a <see cref="CertificateSubject"/>.
189188
/// </summary>
190189
/// <returns>
191-
/// The formatted certificate subject string.
190+
/// The <see cref="CertificateSubject"/> representing the generated subject.
192191
/// </returns>
193-
public static string GetSubjectNameFromUser()
192+
public static CertificateSubject GetSubjectFromUser()
194193
{
195194
string? commonName = null;
196195

@@ -224,7 +223,7 @@ public static string GetSubjectNameFromUser()
224223
organizationalUnit: organizationalUnit,
225224
locality: locality,
226225
state: state,
227-
country: country).ToString();
226+
country: country);
228227
}
229228

230229
/// <summary>

0 commit comments

Comments
 (0)