-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCertification.cs
306 lines (260 loc) · 15.7 KB
/
Certification.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
using BigInteger = Org.BouncyCastle.Math.BigInteger;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
namespace CertificateGen
{
public static class Certification
{
public static X509Certificate2 IssueCertificate(string subjectName, X509Certificate2 issuerCertificate,
DateTime expirationDate, string[] subjectAlternativeNames, KeyPurposeID[] usages)
{
// It's self-signed, so these are the same.
string issuerName = issuerCertificate.Subject;
SecureRandom random = GetSecureRandom();
AsymmetricCipherKeyPair subjectKeyPair = GenerateKeyPair(random, 2048);
AsymmetricCipherKeyPair issuerKeyPair = DotNetUtilities.GetKeyPair(issuerCertificate.PrivateKey);
BigInteger serialNumber = GenerateSerialNumber(random);
BigInteger issuerSerialNumber = new BigInteger(issuerCertificate.GetSerialNumber());
const bool isCertificateAuthority = false;
X509Certificate certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
subjectAlternativeNames, issuerName, issuerKeyPair,
issuerSerialNumber, isCertificateAuthority, expirationDate,
usages);
return ConvertCertificate(certificate, subjectKeyPair, random);
}
public static X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName,
DateTime expirationDate, string[] subjectAlternativeNames, KeyPurposeID[] usages)
{
// It's self-signed, so these are the same.
string issuerName = subjectName;
SecureRandom random = GetSecureRandom();
AsymmetricCipherKeyPair subjectKeyPair = GenerateKeyPair(random, 2048);
// It's self-signed, so these are the same.
AsymmetricCipherKeyPair issuerKeyPair = subjectKeyPair;
BigInteger serialNumber = GenerateSerialNumber(random);
BigInteger issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number.
const bool isCertificateAuthority = true;
X509Certificate certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
subjectAlternativeNames, issuerName, issuerKeyPair,
issuerSerialNumber, isCertificateAuthority, expirationDate,
usages);
return ConvertCertificate(certificate, subjectKeyPair, random);
}
//private static X509Certificate2 CreateSelfSignedCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages)
//{
// // It's self-signed, so these are the same.
// string issuerName = subjectName;
// SecureRandom random = GetSecureRandom();
// AsymmetricCipherKeyPair subjectKeyPair = GenerateKeyPair(random, 2048);
// // It's self-signed, so these are the same.
// AsymmetricCipherKeyPair issuerKeyPair = subjectKeyPair;
// BigInteger serialNumber = GenerateSerialNumber(random);
// BigInteger issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number.
// const bool isCertificateAuthority = false;
// X509Certificate certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
// subjectAlternativeNames, issuerName, issuerKeyPair,
// issuerSerialNumber, isCertificateAuthority,
// usages);
// return ConvertCertificate(certificate, subjectKeyPair, random);
//}
// This password is the one attached to the PFX file. Use 'null' for no password.
public static void WriteCertificate(X509Certificate2 certificate, string outputFileName, string password)
{
byte[] bytes = certificate.Export(X509ContentType.Pfx, password);
File.WriteAllBytes(outputFileName, bytes);
}
private static SecureRandom GetSecureRandom()
{
// Since we're on Windows, we'll use the CryptoAPI one (on the assumption
// that it might have access to better sources of entropy than the built-in
// Bouncy Castle ones):
CryptoApiRandomGenerator randomGenerator = new CryptoApiRandomGenerator();
SecureRandom random = new SecureRandom(randomGenerator);
return random;
}
private static X509Certificate GenerateCertificate(SecureRandom random,
string subjectName,
AsymmetricCipherKeyPair subjectKeyPair,
BigInteger subjectSerialNumber,
string[] subjectAlternativeNames,
string issuerName,
AsymmetricCipherKeyPair issuerKeyPair,
BigInteger issuerSerialNumber,
bool isCertificateAuthority,
DateTime expirationDate,
KeyPurposeID[] usages)
{
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
certificateGenerator.SetSerialNumber(subjectSerialNumber);
X509Name issuerDN = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDN);
// Note: The subject can be omitted if you specify a subject alternative name (SAN).
X509Name subjectDN = new X509Name(subjectName);
certificateGenerator.SetSubjectDN(subjectDN);
// Our certificate needs valid from/to values.
certificateGenerator.SetNotBefore(DateTime.Now.ToUniversalTime());
certificateGenerator.SetNotAfter(expirationDate.ToUniversalTime());
// The subject's public key goes in the certificate.
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
AddAuthorityKeyIdentifier(certificateGenerator, issuerDN, issuerKeyPair, issuerSerialNumber);
AddSubjectKeyIdentifier(certificateGenerator, subjectKeyPair);
AddBasicConstraints(certificateGenerator, isCertificateAuthority);
if (usages != null && usages.Any())
{
AddExtendedKeyUsage(certificateGenerator, usages);
}
if (subjectAlternativeNames != null && subjectAlternativeNames.Any())
{
AddSubjectAlternativeNames(certificateGenerator, subjectAlternativeNames);
}
// The certificate is signed with the issuer's private key.
ISignatureFactory signatureFactory = new Asn1SignatureFactory(PkcsObjectIdentifiers.Sha256WithRsaEncryption.ToString(), subjectKeyPair.Private, random);
X509Certificate certificate = certificateGenerator.Generate(signatureFactory);
return certificate;
}
/// <summary>
/// The certificate needs a serial number. This is used for revocation,
/// and usually should be an incrementing index (which makes it easier to revoke a range of certificates).
/// Since we don't have anywhere to store the incrementing index, we can just use a random number.
/// </summary>
/// <param name="random"></param>
/// <returns></returns>
private static BigInteger GenerateSerialNumber(SecureRandom random)
{
BigInteger serialNumber =
BigIntegers.CreateRandomInRange(
BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
return serialNumber;
}
/// <summary>
/// Generate a key pair.
/// </summary>
/// <param name="random">The random number generator.</param>
/// <param name="strength">The key length in bits. For RSA, 2048 bits should be considered the minimum acceptable these days.</param>
/// <returns></returns>
private static AsymmetricCipherKeyPair GenerateKeyPair(SecureRandom random, int strength)
{
KeyGenerationParameters keyGenerationParameters = new KeyGenerationParameters(random, strength);
RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair();
return subjectKeyPair;
}
/// <summary>
/// Add the Authority Key Identifier. According to http://www.alvestrand.no/objectid/2.5.29.35.html, this
/// identifies the public key to be used to verify the signature on this certificate.
/// In a certificate chain, this corresponds to the "Subject Key Identifier" on the *issuer* certificate.
/// The Bouncy Castle documentation, at http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation,
/// shows how to create this from the issuing certificate. Since we're creating a self-signed certificate, we have to do this slightly differently.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="issuerDN"></param>
/// <param name="issuerKeyPair"></param>
/// <param name="issuerSerialNumber"></param>
private static void AddAuthorityKeyIdentifier(X509V3CertificateGenerator certificateGenerator,
X509Name issuerDN,
AsymmetricCipherKeyPair issuerKeyPair,
BigInteger issuerSerialNumber)
{
AuthorityKeyIdentifier authorityKeyIdentifierExtension =
new AuthorityKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(issuerKeyPair.Public),
new GeneralNames(new GeneralName(issuerDN)),
issuerSerialNumber);
certificateGenerator.AddExtension(
X509Extensions.AuthorityKeyIdentifier.Id, false, authorityKeyIdentifierExtension);
}
/// <summary>
/// Add the "Subject Alternative Names" extension. Note that you have to repeat
/// the value from the "Subject Name" property.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="subjectAlternativeNames"></param>
private static void AddSubjectAlternativeNames(X509V3CertificateGenerator certificateGenerator,
IEnumerable<string> subjectAlternativeNames)
{
DerSequence subjectAlternativeNamesExtension =
new DerSequence(
subjectAlternativeNames.Select(name => new GeneralName(GeneralName.DnsName, name))
.ToArray<Asn1Encodable>());
certificateGenerator.AddExtension(
X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
}
/// <summary>
/// Add the "Extended Key Usage" extension, specifying (for example) "server authentication".
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="usages"></param>
private static void AddExtendedKeyUsage(X509V3CertificateGenerator certificateGenerator, KeyPurposeID[] usages)
{
certificateGenerator.AddExtension(
X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(usages));
}
/// <summary>
/// Add the "Basic Constraints" extension.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="isCertificateAuthority"></param>
private static void AddBasicConstraints(X509V3CertificateGenerator certificateGenerator,
bool isCertificateAuthority)
{
certificateGenerator.AddExtension(
X509Extensions.BasicConstraints.Id, true, new BasicConstraints(isCertificateAuthority));
}
/// <summary>
/// Add the Subject Key Identifier.
/// </summary>
/// <param name="certificateGenerator"></param>
/// <param name="subjectKeyPair"></param>
private static void AddSubjectKeyIdentifier(X509V3CertificateGenerator certificateGenerator,
AsymmetricCipherKeyPair subjectKeyPair)
{
SubjectKeyIdentifier subjectKeyIdentifierExtension =
new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(subjectKeyPair.Public));
certificateGenerator.AddExtension(
X509Extensions.SubjectKeyIdentifier.Id, false, subjectKeyIdentifierExtension);
}
private static X509Certificate2 ConvertCertificate(X509Certificate certificate,
AsymmetricCipherKeyPair subjectKeyPair,
SecureRandom random)
{
// Now to convert the Bouncy Castle certificate to a .NET certificate.
// See http://web.archive.org/web/20100504192226/http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx
// ...but, basically, we create a PKCS12 store (a .PFX file) in memory, and add the public and private key to that.
Pkcs12Store store = new Pkcs12Store();
// What Bouncy Castle calls "alias" is the same as what Windows terms the "friendly name".
string friendlyName = certificate.SubjectDN.ToString();
// Add the certificate.
X509CertificateEntry certificateEntry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(friendlyName, certificateEntry);
// Add the private key.
store.SetKeyEntry(friendlyName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });
// Convert it to an X509Certificate2 object by saving/loading it from a MemoryStream.
// It needs a password. Since we'll remove this later, it doesn't particularly matter what we use.
const string password = "password";
MemoryStream stream = new MemoryStream();
store.Save(stream, password.ToCharArray(), random);
X509Certificate2 convertedCertificate =
new X509Certificate2(stream.ToArray(),
password,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
return convertedCertificate;
}
}
}