/
CertUtils.cs
142 lines (121 loc) · 5.43 KB
/
CertUtils.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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Certify.Management;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.X509;
namespace Certify.Shared.Core.Utils.PKI
{
/// <summary>
/// Terminology from https://en.wikipedia.org/wiki/Chain_of_trust
/// </summary>
[Flags]
public enum ExportFlags
{
EndEntityCertificate = 1,
IntermediateCertificates = 4,
RootCertificate = 6,
PrivateKey = 8
}
public static class CertUtils
{
/// <summary>
/// Get PEM encoded cert bytes (intermediates only or full chain) from PFX bytes
/// </summary>
/// <param name="pfxData"></param>
/// <param name="pwd">private key password</param>
/// <param name="flags">Flags for component types to export</param>
/// <returns></returns>
public static byte[] GetCertComponentsAsPEMBytes(byte[] pfxData, string pwd, ExportFlags flags)
{
var pem = GetCertComponentsAsPEMString(pfxData, pwd, flags);
return System.Text.Encoding.ASCII.GetBytes(pem);
}
public static string GetCertComponentsAsPEMString(byte[] pfxData, string pwd, ExportFlags flags)
{
// See also https://www.digicert.com/ssl-support/pem-ssl-creation.htm
var cert = new X509Certificate2(pfxData, pwd);
var chain = new X509Chain();
chain.Build(cert);
using (var writer = new StringWriter())
{
var certParser = new X509CertificateParser();
var pemWriter = new PemWriter(writer);
//output in order of private key, primary cert, intermediates, root
if (flags.HasFlag(ExportFlags.PrivateKey))
{
var key = GetCertKeyPem(pfxData, pwd);
writer.Write(key);
}
var i = 0;
foreach (var c in chain.ChainElements)
{
if (i == 0 && flags.HasFlag(ExportFlags.EndEntityCertificate))
{
// first cert is end entity cert (primary certificate)
var o = c.Certificate.Export(X509ContentType.Cert);
pemWriter.WriteObject(certParser.ReadCertificate(o));
}
else if (i == chain.ChainElements.Count - 1 && flags.HasFlag(ExportFlags.RootCertificate))
{
// last cert is root ca public cert
var o = c.Certificate.Export(X509ContentType.Cert);
pemWriter.WriteObject(certParser.ReadCertificate(o));
}
else if (i != 0 && (i != chain.ChainElements.Count - 1) && flags.HasFlag(ExportFlags.IntermediateCertificates))
{
// intermediate cert(s), if any, not including end entity and root
var o = c.Certificate.Export(X509ContentType.Cert);
pemWriter.WriteObject(certParser.ReadCertificate(o));
}
i++;
}
writer.Flush();
return writer.ToString();
}
}
/// <summary>
/// Get PEM encoded private key bytes from PFX bytes
/// </summary>
/// <param name="pfxData"></param>
/// <param name="pwd"></param>
/// <returns></returns>
public static string GetCertKeyPem(byte[] pfxData, string pwd)
{
var pkcsStore = new Pkcs12StoreBuilder().Build();
pkcsStore.Load(new MemoryStream(pfxData), pwd.ToCharArray());
var keyAlias = pkcsStore.Aliases
.OfType<string>()
.Where(a => pkcsStore.IsKeyEntry(a))
.FirstOrDefault();
var key = pkcsStore.GetKey(keyAlias).Key;
using (var writer = new StringWriter())
{
new PemWriter(writer).WriteObject(key);
writer.Flush();
return writer.ToString();
}
}
/// <summary>
/// For a given PFX, calculate the Base64Url encoded ARI CertID based on
/// base64url(Authority Key Identifier) + "." + base64url(Serial).
/// See draft-ietf-acme-ari-03
/// </summary>
/// <param name="sourceCert"></param>
/// <returns>ARI Certificate ID</returns>
public static string GetARICertIdBase64(X509Certificate2 sourceCert)
{
// we use BC for the AKI and Serial because native netfx and dotnet are inconsistent and may returns the serial in reverse
var cert = new Org.BouncyCastle.X509.X509CertificateParser().ReadCertificate(sourceCert.GetRawCertData());
// https://letsencrypt.org/2024/04/25/guide-to-integrating-ari-into-existing-acme-clients
var certAKI = AuthorityKeyIdentifier.GetInstance(cert.GetExtensionValue(X509Extensions.AuthorityKeyIdentifier).GetOctets());
var certAKIbytes = certAKI.GetKeyIdentifier();
var certSerialBytes = cert.SerialNumber.ToByteArray();
var certId = $"{Util.ToUrlSafeBase64String(certAKIbytes)}.{Util.ToUrlSafeBase64String(certSerialBytes)}";
return certId;
}
}
}