Skip to content
This repository was archived by the owner on Jul 9, 2023. It is now read-only.

Commit a5a04e9

Browse files
committed
fast BC
1 parent 147d723 commit a5a04e9

File tree

3 files changed

+253
-4
lines changed

3 files changed

+253
-4
lines changed

src/Titanium.Web.Proxy/Network/Certificate/BCCertificateMaker.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ internal BCCertificateMaker(ExceptionHandler exceptionFunc)
4444
/// Makes the certificate.
4545
/// </summary>
4646
/// <param name="sSubjectCn">The s subject cn.</param>
47-
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
4847
/// <param name="signingCert">The signing cert.</param>
4948
/// <returns>X509Certificate2 instance.</returns>
5049
public X509Certificate2 MakeCertificate(string sSubjectCn, X509Certificate2? signingCert = null)
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
using System;
2+
using System.IO;
3+
using System.Security.Cryptography.X509Certificates;
4+
using Org.BouncyCastle.Asn1;
5+
using Org.BouncyCastle.Asn1.Pkcs;
6+
using Org.BouncyCastle.Asn1.X509;
7+
using Org.BouncyCastle.Crypto;
8+
using Org.BouncyCastle.Crypto.Generators;
9+
using Org.BouncyCastle.Crypto.Operators;
10+
using Org.BouncyCastle.Crypto.Parameters;
11+
using Org.BouncyCastle.Crypto.Prng;
12+
using Org.BouncyCastle.Math;
13+
using Org.BouncyCastle.OpenSsl;
14+
using Org.BouncyCastle.Pkcs;
15+
using Org.BouncyCastle.Security;
16+
using Org.BouncyCastle.Utilities;
17+
using Org.BouncyCastle.X509;
18+
using Titanium.Web.Proxy.Helpers;
19+
using Titanium.Web.Proxy.Shared;
20+
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
21+
22+
namespace Titanium.Web.Proxy.Network.Certificate
23+
{
24+
/// <summary>
25+
/// Implements certificate generation operations.
26+
/// </summary>
27+
internal class BCCertificateMakerFast : ICertificateMaker
28+
{
29+
private const int certificateValidDays = 1825;
30+
private const int certificateGraceDays = 366;
31+
32+
// The FriendlyName value cannot be set on Unix.
33+
// Set this flag to true when exception detected to avoid further exceptions
34+
private static bool doNotSetFriendlyName;
35+
36+
private readonly ExceptionHandler exceptionFunc;
37+
38+
public AsymmetricCipherKeyPair KeyPair { get; set; }
39+
40+
internal BCCertificateMakerFast(ExceptionHandler exceptionFunc)
41+
{
42+
this.exceptionFunc = exceptionFunc;
43+
KeyPair = GenerateKeyPair();
44+
}
45+
46+
/// <summary>
47+
/// Makes the certificate.
48+
/// </summary>
49+
/// <param name="sSubjectCn">The s subject cn.</param>
50+
/// <param name="signingCert">The signing cert.</param>
51+
/// <returns>X509Certificate2 instance.</returns>
52+
public X509Certificate2 MakeCertificate(string sSubjectCn, X509Certificate2? signingCert = null)
53+
{
54+
return makeCertificateInternal(sSubjectCn, true, signingCert);
55+
}
56+
57+
/// <summary>
58+
/// Generates the certificate.
59+
/// </summary>
60+
/// <param name="subjectName">Name of the subject.</param>
61+
/// <param name="issuerName">Name of the issuer.</param>
62+
/// <param name="validFrom">The valid from.</param>
63+
/// <param name="validTo">The valid to.</param>
64+
/// <param name="subjectKeyPair">The key pair.</param>
65+
/// <param name="signatureAlgorithm">The signature algorithm.</param>
66+
/// <param name="issuerPrivateKey">The issuer private key.</param>
67+
/// <param name="hostName">The host name</param>
68+
/// <returns>X509Certificate2 instance.</returns>
69+
/// <exception cref="PemException">Malformed sequence in RSA private key</exception>
70+
private static X509Certificate2 generateCertificate(string? hostName,
71+
string subjectName,
72+
string issuerName, DateTime validFrom,
73+
DateTime validTo, AsymmetricCipherKeyPair subjectKeyPair,
74+
string signatureAlgorithm = "SHA256WithRSA",
75+
AsymmetricKeyParameter? issuerPrivateKey = null)
76+
{
77+
// Generating Random Numbers
78+
var randomGenerator = new CryptoApiRandomGenerator();
79+
var secureRandom = new SecureRandom(randomGenerator);
80+
81+
// The Certificate Generator
82+
var certificateGenerator = new X509V3CertificateGenerator();
83+
84+
// Serial Number
85+
var serialNumber =
86+
BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), secureRandom);
87+
certificateGenerator.SetSerialNumber(serialNumber);
88+
89+
// Issuer and Subject Name
90+
var subjectDn = new X509Name(subjectName);
91+
var issuerDn = new X509Name(issuerName);
92+
certificateGenerator.SetIssuerDN(issuerDn);
93+
certificateGenerator.SetSubjectDN(subjectDn);
94+
95+
certificateGenerator.SetNotBefore(validFrom);
96+
certificateGenerator.SetNotAfter(validTo);
97+
98+
if (hostName != null)
99+
{
100+
// add subject alternative names
101+
var subjectAlternativeNames = new Asn1Encodable[] { new GeneralName(GeneralName.DnsName, hostName) };
102+
103+
var subjectAlternativeNamesExtension = new DerSequence(subjectAlternativeNames);
104+
certificateGenerator.AddExtension(X509Extensions.SubjectAlternativeName.Id, false,
105+
subjectAlternativeNamesExtension);
106+
}
107+
108+
// Subject Public Key
109+
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
110+
111+
// Set certificate intended purposes to only Server Authentication
112+
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false,
113+
new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
114+
if (issuerPrivateKey == null)
115+
{
116+
certificateGenerator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(true));
117+
}
118+
119+
var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm,
120+
issuerPrivateKey ?? subjectKeyPair.Private, secureRandom);
121+
122+
// Self-sign the certificate
123+
var certificate = certificateGenerator.Generate(signatureFactory);
124+
125+
// Corresponding private key
126+
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
127+
128+
var seq = (Asn1Sequence)Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded());
129+
130+
if (seq.Count != 9)
131+
{
132+
throw new PemException("Malformed sequence in RSA private key");
133+
}
134+
135+
var rsa = RsaPrivateKeyStructure.GetInstance(seq);
136+
var rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent,
137+
rsa.Prime1, rsa.Prime2, rsa.Exponent1,
138+
rsa.Exponent2, rsa.Coefficient);
139+
140+
// Set private key onto certificate instance
141+
var x509Certificate = withPrivateKey(certificate, rsaparams);
142+
143+
if (!doNotSetFriendlyName)
144+
{
145+
try
146+
{
147+
x509Certificate.FriendlyName = ProxyConstants.CNRemoverRegex.Replace(subjectName, string.Empty);
148+
}
149+
catch (PlatformNotSupportedException)
150+
{
151+
doNotSetFriendlyName = true;
152+
}
153+
}
154+
155+
return x509Certificate;
156+
}
157+
158+
public AsymmetricCipherKeyPair GenerateKeyPair(int keyStrength = 2048)
159+
{
160+
var randomGenerator = new CryptoApiRandomGenerator();
161+
var secureRandom = new SecureRandom(randomGenerator);
162+
163+
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength);
164+
var keyPairGenerator = new RsaKeyPairGenerator();
165+
keyPairGenerator.Init(keyGenerationParameters);
166+
return keyPairGenerator.GenerateKeyPair();
167+
}
168+
169+
private static X509Certificate2 withPrivateKey(X509Certificate certificate, AsymmetricKeyParameter privateKey)
170+
{
171+
const string password = "password";
172+
Pkcs12Store store;
173+
174+
if(RunTime.IsRunningOnMono)
175+
{
176+
var builder = new Pkcs12StoreBuilder();
177+
builder.SetUseDerEncoding(true);
178+
store = builder.Build();
179+
}
180+
else
181+
{
182+
store = new Pkcs12Store();
183+
}
184+
185+
var entry = new X509CertificateEntry(certificate);
186+
store.SetCertificateEntry(certificate.SubjectDN.ToString(), entry);
187+
188+
store.SetKeyEntry(certificate.SubjectDN.ToString(), new AsymmetricKeyEntry(privateKey), new[] { entry });
189+
using (var ms = new MemoryStream())
190+
{
191+
store.Save(ms, password.ToCharArray(), new SecureRandom(new CryptoApiRandomGenerator()));
192+
193+
return new X509Certificate2(ms.ToArray(), password, X509KeyStorageFlags.Exportable);
194+
}
195+
}
196+
197+
/// <summary>
198+
/// Makes the certificate internal.
199+
/// </summary>
200+
/// <param name="hostName">hostname for certificate</param>
201+
/// <param name="subjectName">The full subject.</param>
202+
/// <param name="validFrom">The valid from.</param>
203+
/// <param name="validTo">The valid to.</param>
204+
/// <param name="signingCertificate">The signing certificate.</param>
205+
/// <returns>X509Certificate2 instance.</returns>
206+
/// <exception cref="System.ArgumentException">
207+
/// You must specify a Signing Certificate if and only if you are not creating a
208+
/// root.
209+
/// </exception>
210+
private X509Certificate2 makeCertificateInternal(string hostName, string subjectName,
211+
DateTime validFrom, DateTime validTo, X509Certificate2? signingCertificate)
212+
{
213+
if (signingCertificate == null)
214+
{
215+
return generateCertificate(null, subjectName, subjectName, validFrom, validTo, KeyPair);
216+
}
217+
218+
var kp = DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey);
219+
return generateCertificate(hostName, subjectName, signingCertificate.Subject, validFrom, validTo, KeyPair,
220+
issuerPrivateKey: kp.Private);
221+
}
222+
223+
/// <summary>
224+
/// Makes the certificate internal.
225+
/// </summary>
226+
/// <param name="subject">The s subject cn.</param>
227+
/// <param name="switchToMtaIfNeeded">if set to <c>true</c> [switch to MTA if needed].</param>
228+
/// <param name="signingCert">The signing cert.</param>
229+
/// <returns>X509Certificate2.</returns>
230+
private X509Certificate2 makeCertificateInternal(string subject,
231+
bool switchToMtaIfNeeded, X509Certificate2? signingCert = null)
232+
{
233+
return makeCertificateInternal(subject, $"CN={subject}",
234+
DateTime.UtcNow.AddDays(-certificateGraceDays), DateTime.UtcNow.AddDays(certificateValidDays),
235+
signingCert);
236+
}
237+
}
238+
}

src/Titanium.Web.Proxy/Network/CertificateManager.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public enum CertificateEngine
2424
/// </summary>
2525
BouncyCastle = 0,
2626

27+
BouncyCastleFast = 2,
28+
2729
/// <summary>
2830
/// Uses Windows Certification Generation API and only valid in Windows OS.
2931
/// Observed to be faster than BouncyCastle.
@@ -68,9 +70,19 @@ private ICertificateMaker certEngine
6870
{
6971
if (certEngineValue == null)
7072
{
71-
certEngineValue = engine == CertificateEngine.BouncyCastle
72-
? (ICertificateMaker)new BCCertificateMaker(ExceptionFunc)
73-
: new WinCertificateMaker(ExceptionFunc);
73+
switch (engine)
74+
{
75+
case CertificateEngine.BouncyCastle:
76+
certEngineValue = new BCCertificateMaker(ExceptionFunc);
77+
break;
78+
case CertificateEngine.BouncyCastleFast:
79+
certEngineValue = new BCCertificateMakerFast(ExceptionFunc);
80+
break;
81+
case CertificateEngine.DefaultWindows:
82+
default:
83+
certEngineValue = new WinCertificateMaker(ExceptionFunc);
84+
break;
85+
}
7486
}
7587

7688
return certEngineValue;

0 commit comments

Comments
 (0)