Commit 34899ebf authored by Honfika's avatar Honfika

#121: Allow to set the root dertificate, #149:

 do not create root certificate when it is not needed
parent 8e254c99
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
...@@ -9,10 +10,10 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -9,10 +10,10 @@ namespace Titanium.Web.Proxy.Examples.Basic
{ {
public class ProxyTestController public class ProxyTestController
{ {
private ProxyServer proxyServer; private readonly ProxyServer proxyServer;
//share requestBody outside handlers //share requestBody outside handlers
private Dictionary<Guid, string> requestBodyHistory; private readonly Dictionary<Guid, string> requestBodyHistory = new Dictionary<Guid, string>();
public ProxyTestController() public ProxyTestController()
{ {
...@@ -21,9 +22,10 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -21,9 +22,10 @@ namespace Titanium.Web.Proxy.Examples.Basic
//optionally set the Certificate Engine //optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported //Under Mono only BouncyCastle will be supported
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle; //proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
requestBodyHistory = new Dictionary<Guid, string>(); //optionally set the Root Certificate
//proxyServer.RootCertificate = new X509Certificate2("myCert.pfx", string.Empty, X509KeyStorageFlags.Exportable);
} }
public void StartProxy() public void StartProxy()
......
...@@ -31,6 +31,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -31,6 +31,10 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
internal class CertificateManager : IDisposable internal class CertificateManager : IDisposable
{ {
private const string DefaultRootCertificateIssuer = "Titanium";
private const string DefaultRootRootCertificateName = "Titanium Root Certificate Authority";
private readonly ICertificateMaker certEngine; private readonly ICertificateMaker certEngine;
private bool clearCertificates { get; set; } private bool clearCertificates { get; set; }
...@@ -66,8 +70,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -66,8 +70,8 @@ namespace Titanium.Web.Proxy.Network
certEngine = new WinCertificateMaker(); certEngine = new WinCertificateMaker();
} }
Issuer = issuer; Issuer = issuer ?? DefaultRootCertificateIssuer;
RootCertificateName = rootCertificateName; RootCertificateName = rootCertificateName ?? DefaultRootRootCertificateName;
certificateCache = new ConcurrentDictionary<string, CachedCertificate>(); certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
} }
...@@ -88,7 +92,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -88,7 +92,7 @@ namespace Titanium.Web.Proxy.Network
return fileName; return fileName;
} }
internal X509Certificate2 GetRootCertificate() internal X509Certificate2 LoadRootCertificate()
{ {
var fileName = GetRootCertificatePath(); var fileName = GetRootCertificatePath();
if (!File.Exists(fileName)) return null; if (!File.Exists(fileName)) return null;
...@@ -108,11 +112,16 @@ namespace Titanium.Web.Proxy.Network ...@@ -108,11 +112,16 @@ namespace Titanium.Web.Proxy.Network
/// <returns>true if succeeded, else false</returns> /// <returns>true if succeeded, else false</returns>
internal bool CreateTrustedRootCertificate() internal bool CreateTrustedRootCertificate()
{ {
RootCertificate = GetRootCertificate(); if (RootCertificate == null)
{
RootCertificate = LoadRootCertificate();
}
if (RootCertificate != null) if (RootCertificate != null)
{ {
return true; return true;
} }
try try
{ {
RootCertificate = CreateCertificate(RootCertificateName, true); RootCertificate = CreateCertificate(RootCertificateName, true);
...@@ -121,6 +130,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -121,6 +130,7 @@ namespace Titanium.Web.Proxy.Network
{ {
exceptionFunc(e); exceptionFunc(e);
} }
if (RootCertificate != null) if (RootCertificate != null)
{ {
try try
...@@ -133,9 +143,23 @@ namespace Titanium.Web.Proxy.Network ...@@ -133,9 +143,23 @@ namespace Titanium.Web.Proxy.Network
exceptionFunc(e); exceptionFunc(e);
} }
} }
return RootCertificate != null; return RootCertificate != null;
} }
/// <summary>
/// Trusts the root certificate.
/// </summary>
/// <param name="exceptionFunc"></param>
internal void TrustRootCertificate(Action<Exception> exceptionFunc)
{
//current user
TrustRootCertificate(StoreLocation.CurrentUser, exceptionFunc);
//current system
TrustRootCertificate(StoreLocation.LocalMachine, exceptionFunc);
}
/// <summary> /// <summary>
/// Create an SSL certificate /// Create an SSL certificate
/// </summary> /// </summary>
...@@ -144,15 +168,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -144,15 +168,13 @@ namespace Titanium.Web.Proxy.Network
/// <returns></returns> /// <returns></returns>
internal virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate) internal virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{ {
if (certificateCache.ContainsKey(certificateName)) if (certificateCache.ContainsKey(certificateName))
{ {
var cached = certificateCache[certificateName]; var cached = certificateCache[certificateName];
cached.LastAccess = DateTime.Now; cached.LastAccess = DateTime.Now;
return cached.Certificate; return cached.Certificate;
} }
X509Certificate2 certificate = null; X509Certificate2 certificate = null;
lock (string.Intern(certificateName)) lock (string.Intern(certificateName))
{ {
...@@ -160,6 +182,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -160,6 +182,11 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
if (!isRootCertificate && RootCertificate == null)
{
CreateTrustedRootCertificate();
}
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate); certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate);
} }
catch (Exception e) catch (Exception e)
...@@ -168,7 +195,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -168,7 +195,7 @@ namespace Titanium.Web.Proxy.Network
} }
if (certificate != null && !certificateCache.ContainsKey(certificateName)) if (certificate != null && !certificateCache.ContainsKey(certificateName))
{ {
certificateCache.Add(certificateName, new CachedCertificate() { Certificate = certificate }); certificateCache.Add(certificateName, new CachedCertificate { Certificate = certificate });
} }
} }
else else
...@@ -182,10 +209,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -182,10 +209,7 @@ namespace Titanium.Web.Proxy.Network
} }
} }
return certificate; return certificate;
} }
/// <summary> /// <summary>
......
...@@ -19,11 +19,10 @@ namespace Titanium.Web.Proxy ...@@ -19,11 +19,10 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public partial class ProxyServer : IDisposable public partial class ProxyServer : IDisposable
{ {
/// <summary> /// <summary>
/// Is the root certificate used by this proxy is valid? /// Is the root certificate used by this proxy is valid?
/// </summary> /// </summary>
private bool certValidated { get; set; } private bool? certValidated { get; set; }
/// <summary> /// <summary>
/// Is the proxy currently running /// Is the proxy currently running
...@@ -45,6 +44,8 @@ namespace Titanium.Web.Proxy ...@@ -45,6 +44,8 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
private bool trustRootCertificate;
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// A object that creates tcp connection to server
/// </summary> /// </summary>
...@@ -68,25 +69,61 @@ namespace Titanium.Web.Proxy ...@@ -68,25 +69,61 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public int BUFFER_SIZE { get; set; } = 8192; public int BUFFER_SIZE { get; set; } = 8192;
/// <summary>
/// The root certificate
/// </summary>
public X509Certificate2 RootCertificate
{
get { return certificateCacheManager.RootCertificate; }
set { certificateCacheManager.RootCertificate = value; }
}
/// <summary> /// <summary>
/// Name of the root certificate issuer /// Name of the root certificate issuer
/// </summary> /// </summary>
public string RootCertificateIssuerName { get; set; } public string RootCertificateIssuerName
{
get { return certificateCacheManager.Issuer; }
set
{
CreateCertificateCacheManager(certificateCacheManager.RootCertificateName, value);
certValidated = null;
}
}
/// <summary> /// <summary>
/// Name of the root certificate /// Name of the root certificate
/// If no certificate is provided then a default Root Certificate will be created and used /// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate has to be in the proxy exe directory with the private key /// The provided root certificate has to be in the proxy exe directory with the private key
/// The root certificate file should be named as "rootCert.pfx" /// The root certificate file should be named as "rootCert.pfx"
/// </summary> /// </summary>
public string RootCertificateName { get; set; } public string RootCertificateName
{
get { return certificateCacheManager.RootCertificateName; }
set
{
CreateCertificateCacheManager(value, certificateCacheManager.Issuer);
certValidated = null;
}
}
/// <summary> /// <summary>
/// Trust the RootCertificate used by this proxy server /// Trust the RootCertificate used by this proxy server
/// Note that this do not make the client trust the certificate! /// Note that this do not make the client trust the certificate!
/// This would import the root certificate to the certificate store of machine that runs this proxy server /// This would import the root certificate to the certificate store of machine that runs this proxy server
/// </summary> /// </summary>
public bool TrustRootCertificate { get; set; } public bool TrustRootCertificate
{
get { return trustRootCertificate; }
set
{
trustRootCertificate = value;
if (value)
{
EnsureRootCertificate();
}
}
}
/// <summary> /// <summary>
/// Select Certificate Engine /// Select Certificate Engine
...@@ -211,7 +248,9 @@ namespace Titanium.Web.Proxy ...@@ -211,7 +248,9 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
public ProxyServer() : this(null, null) { } public ProxyServer() : this(null, null)
{
}
/// <summary> /// <summary>
/// Constructor. /// Constructor.
...@@ -220,9 +259,6 @@ namespace Titanium.Web.Proxy ...@@ -220,9 +259,6 @@ namespace Titanium.Web.Proxy
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param> /// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName) public ProxyServer(string rootCertificateName, string rootCertificateIssuerName)
{ {
RootCertificateName = rootCertificateName;
RootCertificateIssuerName = rootCertificateIssuerName;
//default values //default values
ConnectionTimeOutSeconds = 120; ConnectionTimeOutSeconds = 120;
CertificateCacheTimeOutMinutes = 60; CertificateCacheTimeOutMinutes = 60;
...@@ -233,8 +269,8 @@ namespace Titanium.Web.Proxy ...@@ -233,8 +269,8 @@ namespace Titanium.Web.Proxy
#if !DEBUG #if !DEBUG
new FireFoxProxySettingsManager(); new FireFoxProxySettingsManager();
#endif #endif
RootCertificateName = RootCertificateName ?? "Titanium Root Certificate Authority";
RootCertificateIssuerName = RootCertificateIssuerName ?? "Titanium"; CreateCertificateCacheManager(rootCertificateName, rootCertificateIssuerName);
} }
/// <summary> /// <summary>
...@@ -328,8 +364,10 @@ namespace Titanium.Web.Proxy ...@@ -328,8 +364,10 @@ namespace Titanium.Web.Proxy
.ToList() .ToList()
.ForEach(x => x.IsSystemHttpsProxy = false); .ForEach(x => x.IsSystemHttpsProxy = false);
EnsureRootCertificate();
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (certValidated) if (certValidated == true)
{ {
systemProxySettingsManager.SetHttpsProxy( systemProxySettingsManager.SetHttpsProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Any) |
...@@ -397,23 +435,6 @@ namespace Titanium.Web.Proxy ...@@ -397,23 +435,6 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
certificateCacheManager = new CertificateManager(CertificateEngine,
RootCertificateIssuerName,
RootCertificateName, ExceptionFunc);
certValidated = certificateCacheManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
//current user
certificateCacheManager
.TrustRootCertificate(StoreLocation.CurrentUser, ExceptionFunc);
//current system
certificateCacheManager
.TrustRootCertificate(StoreLocation.LocalMachine, ExceptionFunc);
}
if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null
&& GetCustomUpStreamHttpsProxyFunc == null) && GetCustomUpStreamHttpsProxyFunc == null)
{ {
...@@ -431,6 +452,25 @@ namespace Titanium.Web.Proxy ...@@ -431,6 +452,25 @@ namespace Titanium.Web.Proxy
proxyRunning = true; proxyRunning = true;
} }
private void CreateCertificateCacheManager(string rootCertificateName, string rootCertificateIssuerName)
{
certificateCacheManager = new CertificateManager(CertificateEngine,
rootCertificateIssuerName, rootCertificateName, ExceptionFunc);
}
private void EnsureRootCertificate()
{
if (!certValidated.HasValue)
{
certValidated = certificateCacheManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
certificateCacheManager.TrustRootCertificate(ExceptionFunc);
}
}
}
/// <summary> /// <summary>
/// Gets the system up stream proxy. /// Gets the system up stream proxy.
/// </summary> /// </summary>
......
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