Skip to content

Commit f5f00f3

Browse files
committed
Add email support to CertificateSubject
1 parent 5b8fa24 commit f5f00f3

File tree

3 files changed

+78
-29
lines changed

3 files changed

+78
-29
lines changed

src/EasySign.CommandLine/CertificateSubject.cs

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics.Metrics;
44
using System.Linq;
55
using System.Security.AccessControl;
6+
using System.Security.Cryptography.X509Certificates;
67
using System.Text;
78
using System.Threading.Tasks;
89

@@ -20,6 +21,11 @@ public class CertificateSubject
2021
/// </summary>
2122
public string CommonName { get; set; }
2223

24+
/// <summary>
25+
/// Gets or sets the email address (E) of the certificate subject.
26+
/// </summary>
27+
public string? Email { get; set; }
28+
2329
/// <summary>
2430
/// Gets or sets the organization (O) of the certificate subject.
2531
/// </summary>
@@ -50,6 +56,32 @@ public class CertificateSubject
5056
/// </summary>
5157
public Dictionary<string, string> Unknown { get; } = [];
5258

59+
/// <summary>
60+
/// Initializes a new instance of the <see cref="CertificateSubject"/> class with the specified X509Certificate2 certificate.
61+
/// </summary>
62+
/// <param name="certificate">
63+
/// The certificate to extract the subject from.
64+
/// </param>
65+
public CertificateSubject(X509Certificate2 certificate) : this(certificate.Subject)
66+
{
67+
var commonName = certificate.GetNameInfo(X509NameType.SimpleName, false);
68+
if (string.IsNullOrEmpty(commonName))
69+
{
70+
commonName = certificate.GetNameInfo(X509NameType.DnsName, false);
71+
}
72+
73+
if (!string.IsNullOrEmpty(commonName))
74+
{
75+
CommonName = commonName;
76+
}
77+
78+
var email = certificate.GetNameInfo(X509NameType.EmailName, false);
79+
if (!string.IsNullOrEmpty(email))
80+
{
81+
Email = email;
82+
}
83+
}
84+
5385
/// <summary>
5486
/// Initializes a new instance of the <see cref="CertificateSubject"/> class with the specified subject string.
5587
/// </summary>
@@ -77,6 +109,9 @@ public CertificateSubject(string subject)
77109
case "CN":
78110
CommonName = value;
79111
break;
112+
case "E":
113+
Email = value;
114+
break;
80115
case "O":
81116
Organization = value;
82117
break;
@@ -101,7 +136,7 @@ public CertificateSubject(string subject)
101136

102137
if (string.IsNullOrEmpty(CommonName))
103138
{
104-
throw new ArgumentException("Common Name (CN) is required.");
139+
CommonName = string.Empty;
105140
}
106141
}
107142

@@ -110,16 +145,18 @@ public CertificateSubject(string subject)
110145
/// Initializes a new instance of the <see cref="CertificateSubject"/> class with the specified properties.
111146
/// </summary>
112147
/// <param name="commonName">Common Name (CN) - required.</param>
148+
/// <param name="email">Email (E) - optional.</param>
113149
/// <param name="organization">Organization (O) - optional.</param>
114150
/// <param name="organizationalUnit">Organizational Unit (OU) - optional.</param>
115151
/// <param name="locality">Locality (L) - optional.</param>
116152
/// <param name="state">State or Province (ST) - optional.</param>
117153
/// <param name="country">Country (C) - optional.</param>
118-
public CertificateSubject(string commonName, string? organization, string? organizationalUnit, string? locality, string? state, string? country)
154+
public CertificateSubject(string commonName, string? email, string? organization, string? organizationalUnit, string? locality, string? state, string? country)
119155
{
120156
Ensure.String.IsNotNullOrEmpty(commonName, nameof(commonName));
121157

122158
CommonName = commonName;
159+
Email = email;
123160
Organization = organization;
124161
OrganizationalUnit = organizationalUnit;
125162
Locality = locality;
@@ -141,27 +178,32 @@ public override string ToString()
141178
$"CN={CommonName}"
142179
};
143180

144-
if (!string.IsNullOrWhiteSpace(Organization))
181+
if (!string.IsNullOrEmpty(Email))
182+
{
183+
components.Add($"E={Email}");
184+
}
185+
186+
if (!string.IsNullOrEmpty(Organization))
145187
{
146188
components.Add($"O={Organization}");
147189
}
148190

149-
if (!string.IsNullOrWhiteSpace(OrganizationalUnit))
191+
if (!string.IsNullOrEmpty(OrganizationalUnit))
150192
{
151193
components.Add($"OU={OrganizationalUnit}");
152194
}
153195

154-
if (!string.IsNullOrWhiteSpace(Locality))
196+
if (!string.IsNullOrEmpty(Locality))
155197
{
156198
components.Add($"L={Locality}");
157199
}
158200

159-
if (!string.IsNullOrWhiteSpace(State))
201+
if (!string.IsNullOrEmpty(State))
160202
{
161203
components.Add($"ST={State}");
162204
}
163205

164-
if (!string.IsNullOrWhiteSpace(Country))
206+
if (!string.IsNullOrEmpty(Country))
165207
{
166208
components.Add($"C={Country}");
167209
}

src/EasySign.CommandLine/CertificateUtilities.cs

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using EnsureThat;
99
using Spectre.Console;
1010
using System.Text.RegularExpressions;
11+
using Spectre.Console.Rendering;
1112

1213
namespace SAPTeam.EasySign.CommandLine
1314
{
@@ -31,52 +32,43 @@ public static void DisplayCertificate(params X509Certificate2[] certificates)
3132
int index = 1;
3233
foreach (var certificate in certificates)
3334
{
34-
CertificateSubject subject = new CertificateSubject(certificate.Subject);
35+
CertificateSubject subject = new CertificateSubject(certificate);
3536

3637
if (index > 1)
3738
{
3839
grid.AddRow();
3940
}
4041

41-
if (certificates.Length > 1)
42-
{
43-
grid.AddRow($"Certificate #{index}:");
44-
}
45-
else
46-
{
47-
grid.AddRow("Certificate Info:");
48-
}
49-
50-
grid.AddRow(" Common Name", certificate.GetNameInfo(X509NameType.SimpleName, false));
42+
grid.AddRow($"Certificate {(certificates.Length > 1 ? $"#{index}" : "Info")}:");
43+
grid.AddRow(" Common Name", subject.CommonName);
5144
grid.AddRow(" Issuer Name", certificate.GetNameInfo(X509NameType.SimpleName, true));
5245

53-
string holderEmail = certificate.GetNameInfo(X509NameType.EmailName, false);
54-
if (!string.IsNullOrWhiteSpace(holderEmail))
46+
if (!string.IsNullOrEmpty(subject.Email))
5547
{
56-
grid.AddRow(" Holder Email", holderEmail);
48+
grid.AddRow(" Email Address", subject.Email);
5749
}
5850

59-
if (!string.IsNullOrWhiteSpace(subject.Organization))
51+
if (!string.IsNullOrEmpty(subject.Organization))
6052
{
6153
grid.AddRow(" Organization", subject.Organization);
6254
}
6355

64-
if (!string.IsNullOrWhiteSpace(subject.OrganizationalUnit))
56+
if (!string.IsNullOrEmpty(subject.OrganizationalUnit))
6557
{
6658
grid.AddRow(" Organizational Unit", subject.OrganizationalUnit);
6759
}
6860

69-
if (!string.IsNullOrWhiteSpace(subject.Locality))
61+
if (!string.IsNullOrEmpty(subject.Locality))
7062
{
7163
grid.AddRow(" Locality", subject.Locality);
7264
}
7365

74-
if (!string.IsNullOrWhiteSpace(subject.State))
66+
if (!string.IsNullOrEmpty(subject.State))
7567
{
7668
grid.AddRow(" State", subject.State);
7769
}
7870

79-
if (!string.IsNullOrWhiteSpace(subject.Country))
71+
if (!string.IsNullOrEmpty(subject.Country))
8072
{
8173
grid.AddRow(" Country", subject.Country);
8274
}
@@ -86,6 +78,11 @@ public static void DisplayCertificate(params X509Certificate2[] certificates)
8678
grid.AddRow(" Thumbprint", Regex.Replace(certificate.Thumbprint, "(.{2})(?!$)", "$1:"));
8779
grid.AddRow(" Serial Number", Regex.Replace(certificate.SerialNumber, "(.{2})(?!$)", "$1:"));
8880

81+
if (subject.Unknown.Count > 0)
82+
{
83+
grid.AddRow(new Text(" Other Properties"), new Text(string.Join("\n", subject.Unknown.Select(x => $"{x.Key}={x.Value}"))));
84+
}
85+
8986
index++;
9087
}
9188

@@ -109,6 +106,9 @@ public static string GetSubjectNameFromUser()
109106
commonName = Console.ReadLine();
110107
}
111108

109+
Console.Write("Email (E) (optional): ");
110+
string? email = Console.ReadLine();
111+
112112
Console.Write("Organization (O) (optional): ");
113113
string? organization = Console.ReadLine();
114114

@@ -125,6 +125,7 @@ public static string GetSubjectNameFromUser()
125125
string? country = Console.ReadLine();
126126

127127
return new CertificateSubject(commonName: commonName,
128+
email: email,
128129
organization: organization,
129130
organizationalUnit: organizationalUnit,
130131
locality: locality,

src/EasySign.CommandLine/CommandProvider.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ public Command SelfSign
299299
aliases: ["--commonName", "-cn"],
300300
description: "Common Name for the certificate (e.g., example.com)");
301301

302+
var emailOption = new Option<string>(
303+
aliases: ["--email", "-e"],
304+
description: "Email address (e.g., [email protected])");
305+
302306
var orgOption = new Option<string>(
303307
aliases: ["--organization", "-o"],
304308
description: "Organization name (e.g., Example Inc.)");
@@ -312,7 +316,7 @@ public Command SelfSign
312316
description: "Locality (e.g., New York)");
313317

314318
var stateOption = new Option<string>(
315-
aliases: ["--state", "-s"],
319+
aliases: ["--state", "-st"],
316320
description: "State or Province (e.g., NY)");
317321

318322
var countryOption = new Option<string>(
@@ -322,14 +326,15 @@ public Command SelfSign
322326
var command = new Command("self-sign", "Generate self-signed root CA")
323327
{
324328
cnOption,
329+
emailOption,
325330
orgOption,
326331
ouOption,
327332
locOption,
328333
stateOption,
329334
countryOption,
330335
};
331336

332-
command.SetHandler((string commonName, string organization, string organizationalUnit, string locality, string state, string country) =>
337+
command.SetHandler((string commonName, string email, string organization, string organizationalUnit, string locality, string state, string country) =>
333338
{
334339
if (GetSelfSigningRootCA() != null)
335340
{
@@ -346,6 +351,7 @@ public Command SelfSign
346351
else
347352
{
348353
subject = new CertificateSubject(commonName: commonName,
354+
email: email,
349355
organization: organization,
350356
organizationalUnit: organizationalUnit,
351357
locality: locality,
@@ -361,7 +367,7 @@ public Command SelfSign
361367
}
362368

363369
AnsiConsole.MarkupLine($"[green]Root CA created successfully![/]");
364-
}, cnOption, orgOption, ouOption, locOption, stateOption, countryOption);
370+
}, cnOption, emailOption, orgOption, ouOption, locOption, stateOption, countryOption);
365371

366372
return command;
367373
}

0 commit comments

Comments
 (0)