Skip to content

Commit 5b8fa24

Browse files
committed
Add CertificateSubject class
1 parent 755622f commit 5b8fa24

File tree

4 files changed

+220
-54
lines changed

4 files changed

+220
-54
lines changed

src/EasySign.CommandLine/BundleWorker.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ protected virtual void RunAdd(StatusContext statusContext, string[] files, bool
201201
}
202202
}
203203

204+
/// <summary>
205+
/// Runs the info command.
206+
/// </summary>
207+
/// <param name="statusContext">
208+
/// The status context for interacting with <see cref="AnsiConsole.Status"/>.
209+
/// </param>
204210
protected virtual void RunInfo(StatusContext statusContext)
205211
{
206212
Logger.LogInformation("Running info command");
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.Metrics;
4+
using System.Linq;
5+
using System.Security.AccessControl;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
9+
using EnsureThat;
10+
11+
namespace SAPTeam.EasySign.CommandLine
12+
{
13+
/// <summary>
14+
/// Represents the subject of a certificate.
15+
/// </summary>
16+
public class CertificateSubject
17+
{
18+
/// <summary>
19+
/// Gets or sets the common name (CN) of the certificate subject.
20+
/// </summary>
21+
public string CommonName { get; set; }
22+
23+
/// <summary>
24+
/// Gets or sets the organization (O) of the certificate subject.
25+
/// </summary>
26+
public string? Organization { get; set; }
27+
28+
/// <summary>
29+
/// Gets or sets the organizational unit (OU) of the certificate subject.
30+
/// </summary>
31+
public string? OrganizationalUnit { get; set; }
32+
33+
/// <summary>
34+
/// Gets or sets the locality (L) of the certificate subject.
35+
/// </summary>
36+
public string? Locality { get; set; }
37+
38+
/// <summary>
39+
/// Gets or sets the state or province (ST) of the certificate subject.
40+
/// </summary>
41+
public string? State { get; set; }
42+
43+
/// <summary>
44+
/// Gets or sets the country (C) of the certificate subject.
45+
/// </summary>
46+
public string? Country { get; set; }
47+
48+
/// <summary>
49+
/// Gets a dictionary of unknown keys and their values from the parsed subject string.
50+
/// </summary>
51+
public Dictionary<string, string> Unknown { get; } = [];
52+
53+
/// <summary>
54+
/// Initializes a new instance of the <see cref="CertificateSubject"/> class with the specified subject string.
55+
/// </summary>
56+
/// <param name="subject">
57+
/// The subject string in the comma-delimited format.
58+
/// </param>
59+
public CertificateSubject(string subject)
60+
{
61+
Ensure.String.IsNotNullOrEmpty(subject, nameof(subject));
62+
63+
var parts = subject.Split([","], StringSplitOptions.RemoveEmptyEntries);
64+
65+
foreach (var part in parts)
66+
{
67+
int index = part.IndexOf('=');
68+
if (index <= 0 || index >= part.Length - 1)
69+
continue; // Ignore malformed parts.
70+
71+
string key = part.Substring(0, index).Trim();
72+
string value = part.Substring(index + 1).Trim();
73+
74+
// Map each key abbreviation to a property of the object
75+
switch (key.ToUpperInvariant())
76+
{
77+
case "CN":
78+
CommonName = value;
79+
break;
80+
case "O":
81+
Organization = value;
82+
break;
83+
case "OU":
84+
OrganizationalUnit = value;
85+
break;
86+
case "L":
87+
Locality = value;
88+
break;
89+
case "ST":
90+
case "S":
91+
State = value;
92+
break;
93+
case "C":
94+
Country = value;
95+
break;
96+
default:
97+
Unknown[key.ToUpperInvariant()] = value; // Store unknown keys in the dictionary
98+
break;
99+
}
100+
}
101+
102+
if (string.IsNullOrEmpty(CommonName))
103+
{
104+
throw new ArgumentException("Common Name (CN) is required.");
105+
}
106+
}
107+
108+
109+
/// <summary>
110+
/// Initializes a new instance of the <see cref="CertificateSubject"/> class with the specified properties.
111+
/// </summary>
112+
/// <param name="commonName">Common Name (CN) - required.</param>
113+
/// <param name="organization">Organization (O) - optional.</param>
114+
/// <param name="organizationalUnit">Organizational Unit (OU) - optional.</param>
115+
/// <param name="locality">Locality (L) - optional.</param>
116+
/// <param name="state">State or Province (ST) - optional.</param>
117+
/// <param name="country">Country (C) - optional.</param>
118+
public CertificateSubject(string commonName, string? organization, string? organizationalUnit, string? locality, string? state, string? country)
119+
{
120+
Ensure.String.IsNotNullOrEmpty(commonName, nameof(commonName));
121+
122+
CommonName = commonName;
123+
Organization = organization;
124+
OrganizationalUnit = organizationalUnit;
125+
Locality = locality;
126+
State = state;
127+
Country = country;
128+
}
129+
130+
131+
/// <summary>
132+
/// Generates a comma-delimited string representation of the certificate subject.
133+
/// </summary>
134+
/// <returns>
135+
/// A comma-delimited string representation of the certificate subject.
136+
/// </returns>
137+
public override string ToString()
138+
{
139+
var components = new List<string>
140+
{
141+
$"CN={CommonName}"
142+
};
143+
144+
if (!string.IsNullOrWhiteSpace(Organization))
145+
{
146+
components.Add($"O={Organization}");
147+
}
148+
149+
if (!string.IsNullOrWhiteSpace(OrganizationalUnit))
150+
{
151+
components.Add($"OU={OrganizationalUnit}");
152+
}
153+
154+
if (!string.IsNullOrWhiteSpace(Locality))
155+
{
156+
components.Add($"L={Locality}");
157+
}
158+
159+
if (!string.IsNullOrWhiteSpace(State))
160+
{
161+
components.Add($"ST={State}");
162+
}
163+
164+
if (!string.IsNullOrWhiteSpace(Country))
165+
{
166+
components.Add($"C={Country}");
167+
}
168+
169+
return string.Join(", ", components);
170+
}
171+
}
172+
173+
}

src/EasySign.CommandLine/CertificateUtilities.cs

Lines changed: 35 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public static void DisplayCertificate(params X509Certificate2[] certificates)
3131
int index = 1;
3232
foreach (var certificate in certificates)
3333
{
34+
CertificateSubject subject = new CertificateSubject(certificate.Subject);
35+
3436
if (index > 1)
3537
{
3638
grid.AddRow();
@@ -54,9 +56,35 @@ public static void DisplayCertificate(params X509Certificate2[] certificates)
5456
grid.AddRow(" Holder Email", holderEmail);
5557
}
5658

59+
if (!string.IsNullOrWhiteSpace(subject.Organization))
60+
{
61+
grid.AddRow(" Organization", subject.Organization);
62+
}
63+
64+
if (!string.IsNullOrWhiteSpace(subject.OrganizationalUnit))
65+
{
66+
grid.AddRow(" Organizational Unit", subject.OrganizationalUnit);
67+
}
68+
69+
if (!string.IsNullOrWhiteSpace(subject.Locality))
70+
{
71+
grid.AddRow(" Locality", subject.Locality);
72+
}
73+
74+
if (!string.IsNullOrWhiteSpace(subject.State))
75+
{
76+
grid.AddRow(" State", subject.State);
77+
}
78+
79+
if (!string.IsNullOrWhiteSpace(subject.Country))
80+
{
81+
grid.AddRow(" Country", subject.Country);
82+
}
83+
5784
grid.AddRow(" Valid From", certificate.GetEffectiveDateString());
5885
grid.AddRow(" Valid To", certificate.GetExpirationDateString());
5986
grid.AddRow(" Thumbprint", Regex.Replace(certificate.Thumbprint, "(.{2})(?!$)", "$1:"));
87+
grid.AddRow(" Serial Number", Regex.Replace(certificate.SerialNumber, "(.{2})(?!$)", "$1:"));
6088

6189
index++;
6290
}
@@ -90,64 +118,18 @@ public static string GetSubjectNameFromUser()
90118
Console.Write("Locality (L) (optional): ");
91119
string? locality = Console.ReadLine();
92120

93-
Console.Write("State or Province (S) (optional): ");
121+
Console.Write("State or Province (ST) (optional): ");
94122
string? state = Console.ReadLine();
95123

96124
Console.Write("Country (C) (optional): ");
97125
string? country = Console.ReadLine();
98126

99-
return GenerateSubjectName(commonName, organization, organizationalUnit, locality, state, country);
100-
}
101-
102-
/// <summary>
103-
/// Generates a standardized certificate subject name.
104-
/// Only non-empty components are included.
105-
/// </summary>
106-
/// <param name="commonName">Common Name (CN) - required.</param>
107-
/// <param name="organization">Organization (O) - optional.</param>
108-
/// <param name="organizationalUnit">Organizational Unit (OU) - optional.</param>
109-
/// <param name="locality">Locality (L) - optional.</param>
110-
/// <param name="stateOrProvince">State or Province (S) - optional.</param>
111-
/// <param name="country">Country (C) - optional.</param>
112-
/// <returns>The formatted certificate subject string.</returns>
113-
public static string GenerateSubjectName(string commonName, string? organization, string? organizationalUnit, string? locality, string? stateOrProvince, string? country)
114-
{
115-
Ensure.String.IsNotNullOrEmpty(commonName, nameof(commonName));
116-
117-
var components = new List<string>
118-
{
119-
// Required fields
120-
$"CN={commonName}"
121-
};
122-
123-
// Optional fields: add only if they are not null or empty.
124-
if (!string.IsNullOrWhiteSpace(organization))
125-
{
126-
components.Add($"O={organization}");
127-
}
128-
129-
if (!string.IsNullOrWhiteSpace(organizationalUnit))
130-
{
131-
components.Add($"OU={organizationalUnit}");
132-
}
133-
134-
if (!string.IsNullOrWhiteSpace(locality))
135-
{
136-
components.Add($"L={locality}");
137-
}
138-
139-
if (!string.IsNullOrWhiteSpace(stateOrProvince))
140-
{
141-
components.Add($"S={stateOrProvince}");
142-
}
143-
144-
if (!string.IsNullOrWhiteSpace(country))
145-
{
146-
components.Add($"C={country}");
147-
}
148-
149-
// Combine with comma separators.
150-
return string.Join(", ", components);
127+
return new CertificateSubject(commonName: commonName,
128+
organization: organization,
129+
organizationalUnit: organizationalUnit,
130+
locality: locality,
131+
state: state,
132+
country: country).ToString();
151133
}
152134

153135
/// <summary>

src/EasySign.CommandLine/CommandProvider.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,12 @@ public Command SelfSign
345345
}
346346
else
347347
{
348-
subject = CertificateUtilities.GenerateSubjectName(commonName, organization, organizationalUnit, locality, state, country);
348+
subject = new CertificateSubject(commonName: commonName,
349+
organization: organization,
350+
organizationalUnit: organizationalUnit,
351+
locality: locality,
352+
state: state,
353+
country: country).ToString();
349354
}
350355

351356
var rootCA = CertificateUtilities.CreateSelfSignedCACertificate(subject);

0 commit comments

Comments
 (0)