forked from temporalio/samples-dotnet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGenClientCerts.cs
101 lines (90 loc) · 4.21 KB
/
GenClientCerts.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
namespace TemporalioSamples.ClientMtls;
using System.CommandLine;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
public static class GenClientCerts
{
private static readonly Oid ServerAuthOid = new Oid("1.3.6.1.5.5.7.3.1");
private static readonly Oid ClientAuthOid = new Oid("1.3.6.1.5.5.7.3.2");
public static Command CreateCommand()
{
var cmd = new Command("gen-client-certs", "Generate certificates");
var filePrefixOption = new Option<string>(
name: "--file-prefix",
getDefaultValue: () => string.Empty,
description: "Path and file prefix that generated cert filenames will start with");
var caCertFileOption = new Option<FileInfo?>(
name: "--ca-cert-file",
description: "CA cert to use. If not set, a CA cert/key is generated.");
var caKeyFileOption = new Option<FileInfo?>(
name: "--ca-key-file",
description: "CA key to use. Must be set if --ca-cert-file is, ignored otherwise.");
cmd.SetHandler(Run, filePrefixOption, caCertFileOption, caKeyFileOption);
return cmd;
}
private static void Run(string filePrefix, FileInfo? caCertFile, FileInfo? caKeyFile)
{
// Get CA
using var caCert = LoadOrGenerateCACert(filePrefix, caCertFile, caKeyFile);
// Create cert signed by CA valid for 5 years
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var req = new CertificateRequest("CN=client cert, O=My Org", ecdsa, HashAlgorithmName.SHA256);
req.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(new OidCollection { ServerAuthOid, ClientAuthOid }, false));
var serial = new byte[8];
RandomNumberGenerator.Fill(serial);
using var cert = req.Create(
issuerCertificate: caCert,
notBefore: DateTimeOffset.UtcNow - TimeSpan.FromDays(5),
notAfter: DateTimeOffset.UtcNow + TimeSpan.FromDays(365 * 5),
serialNumber: serial).CopyWithPrivateKey(ecdsa);
// Write to files
WriteCerts($"{filePrefix}client", cert);
}
private static X509Certificate2 LoadOrGenerateCACert(
string filePrefix, FileInfo? caCertFile, FileInfo? caKeyFile)
{
// Load if files are present
if (caCertFile != null)
{
if (caKeyFile == null)
{
throw new ArgumentException("Missing CA key file with CA cert file");
}
return X509Certificate2.CreateFromPemFile(caCertFile.FullName, caKeyFile.FullName);
}
// Build request
using var ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256);
var req = new CertificateRequest("CN=root cert, O=My Org", ecdsa, HashAlgorithmName.SHA256);
req.CertificateExtensions.Add(new X509BasicConstraintsExtension(
certificateAuthority: true,
hasPathLengthConstraint: false,
pathLengthConstraint: 0,
critical: false));
req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyCertSign, false));
req.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(new OidCollection { ServerAuthOid, ClientAuthOid }, false));
// Self-sign with 10 years in either direction since it's the root
var cert = req.CreateSelfSigned(
notBefore: DateTimeOffset.UtcNow - TimeSpan.FromDays(365 * 10),
notAfter: DateTimeOffset.UtcNow + TimeSpan.FromDays(365 * 10));
// Write to files
WriteCerts($"{filePrefix}client-ca", cert);
return cert;
}
private static void WriteCerts(string prefix, X509Certificate2 cert)
{
var certPath = $"{prefix}-cert.pem";
if (File.Exists(certPath))
{
throw new InvalidOperationException($"File {certPath} already exists");
}
var keyPath = $"{prefix}-key.pem";
if (File.Exists(keyPath))
{
throw new InvalidOperationException($"File {keyPath} already exists");
}
File.WriteAllText(certPath, cert.ExportCertificatePem());
File.WriteAllText(keyPath, cert.GetECDsaPrivateKey()!.ExportPkcs8PrivateKeyPem());
}
}