Commit 4e003a1d authored by justcoding121's avatar justcoding121

#188 Support BouncyCastle optionally

parent 8a275276
...@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.UnitTests
{ {
var tasks = new List<Task>(); var tasks = new List<Task>();
var mgr = new CertificateManager("Titanium", "Titanium Root Certificate Authority", var mgr = new CertificateManager(CertificateEngine.DefaultWindows, "Titanium", "Titanium Root Certificate Authority",
new Lazy<Action<Exception>>(() => (e => { })).Value); new Lazy<Action<Exception>>(() => (e => { })).Value);
mgr.ClearIdleCertificates(1); mgr.ClearIdleCertificates(1);
......
using System;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// Run time helpers
/// </summary>
internal class RunTime
{
/// <summary>
/// Checks if current run time is Mono
/// </summary>
/// <returns></returns>
internal static bool IsRunningOnMono()
{
return Type.GetType("Mono.Runtime") != null;
}
}
}
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
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.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Implements certificate generation operations.
/// </summary>
public class BCCertificateMaker : ICertificateMaker
{
private const int CertificateValidDays = 1825;
private const int CertificateGraceDays = 366;
/// <summary>
/// Makes the certificate.
/// </summary>
/// <param name="sSubjectCn">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="signingCert">The signing cert.</param>
/// <returns>X509Certificate2 instance.</returns>
public X509Certificate2 MakeCertificate(string sSubjectCn, bool isRoot, X509Certificate2 signingCert = null)
{
return MakeCertificateInternal(sSubjectCn, isRoot, true, signingCert);
}
/// <summary>
/// Generates the certificate.
/// </summary>
/// <param name="subjectName">Name of the subject.</param>
/// <param name="issuerName">Name of the issuer.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="keyStrength">The key strength.</param>
/// <param name="signatureAlgorithm">The signature algorithm.</param>
/// <param name="issuerPrivateKey">The issuer private key.</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="PemException">Malformed sequence in RSA private key</exception>
private static X509Certificate2 GenerateCertificate(string subjectName,
string issuerName, DateTime validFrom,
DateTime validTo, int keyStrength = 2048,
string signatureAlgorithm = "SHA256WithRSA",
AsymmetricKeyParameter issuerPrivateKey = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var secureRandom = new SecureRandom(randomGenerator);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), secureRandom);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
certificateGenerator.SetNotBefore(validFrom);
certificateGenerator.SetNotAfter(validTo);
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Set certificate intended purposes to only Server Authentication
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey ?? subjectKeyPair.Private, secureRandom);
// Self-sign the certificate
var certificate = certificateGenerator.Generate(signatureFactory);
var x509Certificate = new X509Certificate2(certificate.GetEncoded());
// Corresponding private key
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
var seq = (Asn1Sequence)Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("Malformed sequence in RSA private key");
}
var rsa = RsaPrivateKeyStructure.GetInstance(seq);
var rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
// Set private key onto certificate instance
x509Certificate.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
x509Certificate.FriendlyName = subjectName;
return x509Certificate;
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="fullSubject">The full subject.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="signingCertificate">The signing certificate.</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="System.ArgumentException">You must specify a Signing Certificate if and only if you are not creating a root.</exception>
private X509Certificate2 MakeCertificateInternal(bool isRoot, string fullSubject, DateTime validFrom, DateTime validTo, X509Certificate2 signingCertificate)
{
if (isRoot != (null == signingCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", nameof(signingCertificate));
}
return isRoot
? GenerateCertificate(fullSubject, fullSubject, validFrom, validTo)
: GenerateCertificate(fullSubject, signingCertificate.Subject, validFrom, validTo, issuerPrivateKey: DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey).Private);
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="subject">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="switchToMtaIfNeeded">if set to <c>true</c> [switch to MTA if needed].</param>
/// <param name="signingCert">The signing cert.</param>
/// <returns>X509Certificate2.</returns>
private X509Certificate2 MakeCertificateInternal(string subject, bool isRoot, bool switchToMtaIfNeeded, X509Certificate2 signingCert = null, CancellationToken cancellationToken = default(CancellationToken))
{
X509Certificate2 certificate = null;
if (switchToMtaIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
using (var manualResetEvent = new ManualResetEventSlim(false))
{
ThreadPool.QueueUserWorkItem(o =>
{
certificate = MakeCertificateInternal(subject, isRoot, false, signingCert);
if (!cancellationToken.IsCancellationRequested)
{
manualResetEvent?.Set();
}
});
manualResetEvent.Wait(TimeSpan.FromMinutes(1), cancellationToken: cancellationToken);
}
return certificate;
}
return MakeCertificateInternal(isRoot, $"CN={subject}", DateTime.UtcNow.AddDays(-CertificateGraceDays), DateTime.UtcNow.AddDays(CertificateValidDays), isRoot ? null : signingCert);
}
}
}
\ No newline at end of file
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Abstract interface for different Certificate Maker Engines
/// </summary>
internal interface ICertificateMaker
{
X509Certificate2 MakeCertificate(string sSubjectCn, bool isRoot, X509Certificate2 signingCert);
}
}
...@@ -3,13 +3,13 @@ using System.Reflection; ...@@ -3,13 +3,13 @@ using System.Reflection;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network.Certificate
{ {
/// <summary> /// <summary>
/// Certificate Maker - uses MakeCert /// Certificate Maker - uses MakeCert
/// </summary> /// </summary>
public class CertificateMaker public class WinCertificateMaker: ICertificateMaker
{ {
private readonly Type typeX500DN; private readonly Type typeX500DN;
...@@ -33,12 +33,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -33,12 +33,6 @@ namespace Titanium.Web.Proxy.Network
private readonly Type typeX509Enrollment; private readonly Type typeX509Enrollment;
//private Type typeAlternativeName;
//private Type typeAlternativeNames;
//private Type typeAlternativeNamesExt;
private readonly string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0"; private readonly string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";
private object _SharedPrivateKey; private object _SharedPrivateKey;
...@@ -46,7 +40,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -46,7 +40,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
public CertificateMaker() public WinCertificateMaker()
{ {
typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true); typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true); typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true);
...@@ -59,9 +53,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -59,9 +53,6 @@ namespace Titanium.Web.Proxy.Network
typeBasicConstraints = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionBasicConstraints"); typeBasicConstraints = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionBasicConstraints");
typeSignerCertificate = Type.GetTypeFromProgID("X509Enrollment.CSignerCertificate"); typeSignerCertificate = Type.GetTypeFromProgID("X509Enrollment.CSignerCertificate");
typeX509Enrollment = Type.GetTypeFromProgID("X509Enrollment.CX509Enrollment"); typeX509Enrollment = Type.GetTypeFromProgID("X509Enrollment.CX509Enrollment");
//this.typeAlternativeName = Type.GetTypeFromProgID("X509Enrollment.CAlternativeName");
//this.typeAlternativeNames = Type.GetTypeFromProgID("X509Enrollment.CAlternativeNames");
//this.typeAlternativeNamesExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionAlternativeNames");
} }
/// <summary> /// <summary>
...@@ -214,11 +205,17 @@ namespace Titanium.Web.Proxy.Network ...@@ -214,11 +205,17 @@ namespace Titanium.Web.Proxy.Network
manualResetEvent.Close(); manualResetEvent.Close();
return rCert; return rCert;
} }
var fullSubject = $"CN={sSubjectCN}";//Subject
var HashAlgo = "SHA256"; //Sig Algo //Subject
var GraceDays = -366; //Grace Days var fullSubject = $"CN={sSubjectCN}";
var ValidDays = 1825; //ValiDays //Sig Algo
var keyLength = 2048; //KeyLength var HashAlgo = "SHA256";
//Grace Days
var GraceDays = -366;
//ValiDays
var ValidDays = 1825;
//KeyLength
var keyLength = 2048;
var graceTime = DateTime.Now.AddDays(GraceDays); var graceTime = DateTime.Now.AddDays(GraceDays);
var now = DateTime.Now; var now = DateTime.Now;
......
...@@ -5,15 +5,33 @@ using System.Threading.Tasks; ...@@ -5,15 +5,33 @@ using System.Threading.Tasks;
using System.Linq; using System.Linq;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.IO; using System.IO;
using Titanium.Web.Proxy.Network.Certificate;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
/// <summary>
/// Certificate Engine option
/// </summary>
public enum CertificateEngine
{
/// <summary>
/// Uses Windows Certification Generation API
/// </summary>
DefaultWindows = 0,
/// <summary>
/// Uses BouncyCastle 3rd party library
/// </summary>
BouncyCastle = 1
}
/// <summary> /// <summary>
/// A class to manage SSL certificates used by this proxy server /// A class to manage SSL certificates used by this proxy server
/// </summary> /// </summary>
internal class CertificateManager : IDisposable internal class CertificateManager : IDisposable
{ {
private readonly CertificateMaker certEngine; private readonly ICertificateMaker certEngine;
private bool clearCertificates { get; set; } private bool clearCertificates { get; set; }
/// <summary> /// <summary>
...@@ -28,11 +46,23 @@ namespace Titanium.Web.Proxy.Network ...@@ -28,11 +46,23 @@ namespace Titanium.Web.Proxy.Network
internal X509Certificate2 rootCertificate { get; set; } internal X509Certificate2 rootCertificate { get; set; }
internal CertificateManager(string issuer, string rootCertificateName, Action<Exception> exceptionFunc) internal CertificateManager(CertificateEngine engine,
string issuer,
string rootCertificateName,
Action<Exception> exceptionFunc)
{ {
this.exceptionFunc = exceptionFunc; this.exceptionFunc = exceptionFunc;
certEngine = new CertificateMaker(); //For Mono only Bouncy Castle is supported
if (RunTime.IsRunningOnMono()
|| engine == CertificateEngine.BouncyCastle)
{
certEngine = new BCCertificateMaker();
}
else
{
certEngine = new WinCertificateMaker();
}
Issuer = issuer; Issuer = issuer;
RootCertificateName = rootCertificateName; RootCertificateName = rootCertificateName;
......
...@@ -56,6 +56,9 @@ namespace Titanium.Web.Proxy ...@@ -56,6 +56,9 @@ namespace Titanium.Web.Proxy
private SystemProxyManager systemProxySettingsManager { get; } private SystemProxyManager systemProxySettingsManager { get; }
#if !DEBUG #if !DEBUG
/// <summary>
/// Set firefox to use default system proxy
/// </summary>
private FireFoxProxySettingsManager firefoxProxySettingsManager private FireFoxProxySettingsManager firefoxProxySettingsManager
= new FireFoxProxySettingsManager(); = new FireFoxProxySettingsManager();
#endif #endif
...@@ -85,6 +88,13 @@ namespace Titanium.Web.Proxy ...@@ -85,6 +88,13 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public bool TrustRootCertificate { get; set; } public bool TrustRootCertificate { get; set; }
/// <summary>
/// Select Certificate Engine
/// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default
/// </summary>
public CertificateEngine CertificateEngine { get; set; }
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled /// Broken 100 contunue implementations on server/client may cause problems if enabled
...@@ -127,6 +137,16 @@ namespace Titanium.Web.Proxy ...@@ -127,6 +137,16 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// Is the proxy currently running
/// </summary>
public bool ProxyRunning => proxyRunning;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary> /// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication /// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary> /// </summary>
...@@ -187,16 +207,6 @@ namespace Titanium.Web.Proxy ...@@ -187,16 +207,6 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3; public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
/// <summary>
/// Is the proxy currently running
/// </summary>
public bool ProxyRunning => proxyRunning;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
...@@ -211,6 +221,7 @@ namespace Titanium.Web.Proxy ...@@ -211,6 +221,7 @@ namespace Titanium.Web.Proxy
{ {
RootCertificateName = rootCertificateName; RootCertificateName = rootCertificateName;
RootCertificateIssuerName = rootCertificateIssuerName; RootCertificateIssuerName = rootCertificateIssuerName;
//default values //default values
ConnectionTimeOutSeconds = 120; ConnectionTimeOutSeconds = 120;
CertificateCacheTimeOutMinutes = 60; CertificateCacheTimeOutMinutes = 60;
...@@ -355,7 +366,8 @@ namespace Titanium.Web.Proxy ...@@ -355,7 +366,8 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
certificateCacheManager = new CertificateManager(RootCertificateIssuerName, certificateCacheManager = new CertificateManager(CertificateEngine,
RootCertificateIssuerName,
RootCertificateName, ExceptionFunc); RootCertificateName, ExceptionFunc);
certValidated = certificateCacheManager.CreateTrustedRootCertificate(); certValidated = certificateCacheManager.CreateTrustedRootCertificate();
......
...@@ -44,6 +44,10 @@ ...@@ -44,6 +44,10 @@
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
<HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Net" /> <Reference Include="System.Net" />
<Reference Include="System.configuration" /> <Reference Include="System.configuration" />
...@@ -69,9 +73,12 @@ ...@@ -69,9 +73,12 @@
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" /> <Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" /> <Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Helpers\Network.cs" /> <Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" />
<Compile Include="Http\Responses\GenericResponse.cs" /> <Compile Include="Http\Responses\GenericResponse.cs" />
<Compile Include="Network\CachedCertificate.cs" /> <Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\CertificateMaker.cs" /> <Compile Include="Network\Certificate\WinCertificateMaker.cs" />
<Compile Include="Network\Certificate\BCCertificateMaker.cs" />
<Compile Include="Network\Certificate\ICertificateMaker.cs" />
<Compile Include="Network\ProxyClient.cs" /> <Compile Include="Network\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" /> <Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Exceptions\ProxyAuthorizationException.cs" /> <Compile Include="Exceptions\ProxyAuthorizationException.cs" />
...@@ -119,6 +126,7 @@ ...@@ -119,6 +126,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="app.config" /> <None Include="app.config" />
<None Include="packages.config" />
<None Include="StrongNameKey.snk" /> <None Include="StrongNameKey.snk" />
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="BouncyCastle" version="1.8.1" targetFramework="net45" />
</packages>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment