Commit a988e27d authored by justcoding121's avatar justcoding121

Merge branch 'develop' into beta

parents f2817483 1ce0f7fa
...@@ -32,20 +32,18 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -32,20 +32,18 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
//generate root certificate without storing it in file system //generate root certificate without storing it in file system
//proxyServer.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//proxyServer.CertificateManager.CreateTrustedRootCertificate(false); //proxyServer.CertificateManager.CreateTrustedRootCertificate(false);
//proxyServer.CertificateManager.TrustRootCertificate(); //proxyServer.CertificateManager.TrustRootCertificate();
proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message); proxyServer.ExceptionFunc = exception => Console.WriteLine(exception.Message);
proxyServer.TrustRootCertificate = true;
proxyServer.ForwardToUpstreamGateway = true; proxyServer.ForwardToUpstreamGateway = true;
//optionally set the Certificate Engine //optionally set the Certificate Engine
//Under Mono or Non-Windows runtimes only BouncyCastle will be supported //Under Mono or Non-Windows runtimes only BouncyCastle will be supported
//proxyServer.CertificateEngine = Network.CertificateEngine.DefaultWindows; //proxyServer.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle;
//optionally set the Root Certificate //optionally set the Root Certificate
//proxyServer.RootCertificate = new X509Certificate2("myCert.pfx", string.Empty, X509KeyStorageFlags.Exportable); //proxyServer.CertificateManager.RootCertificate = new X509Certificate2("myCert.pfx", string.Empty, X509KeyStorageFlags.Exportable);
} }
public void StartProxy() public void StartProxy()
...@@ -68,9 +66,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -68,9 +66,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password") //GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
}; };
//Exclude Https addresses you don't want to proxy //Fired when a CONNECT request is received
//Useful for clients that use certificate pinning
//for example google.com and dropbox.com
explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect; explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect;
...@@ -130,10 +126,11 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -130,10 +126,11 @@ namespace Titanium.Web.Proxy.Examples.Basic
private async Task<bool> OnBeforeTunnelConnect(string hostname) private async Task<bool> OnBeforeTunnelConnect(string hostname)
{ {
if (hostname.Contains("amazon.com") || hostname.Contains("paypal.com")) if (hostname.Contains("dropbox.com"))
{ {
//exclude bing.com and google.com from being decrypted //Exclude Https addresses you don't want to proxy
//instead it will be relayed via a secure TCP tunnel //Useful for clients that use certificate pinning
//for example dropbox.com
return await Task.FromResult(true); return await Task.FromResult(true);
} }
else else
...@@ -183,7 +180,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -183,7 +180,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
//Filter URL //Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("yahoo.com")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("yahoo.com"))
{ {
await e.Ok("<!DOCTYPE html>" + e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" + "<html><body><h1>" +
"Website Blocked" + "Website Blocked" +
"</h1>" + "</h1>" +
...@@ -195,7 +192,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -195,7 +192,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
////Redirect example ////Redirect example
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) //if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
//{ //{
// await e.Redirect("https://www.paypal.com"); // e.Redirect("https://www.paypal.com");
//} //}
} }
......
...@@ -62,29 +62,25 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -62,29 +62,25 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public MainWindow() public MainWindow()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
//proxyServer.CertificateEngine = CertificateEngine.DefaultWindows; //proxyServer.CertificateManager.CertificateEngine = CertificateEngine.DefaultWindows;
////Set a password for the .pfx file ////Set a password for the .pfx file
//proxyServer.PfxPassword = "PfxPassword"; //proxyServer.CertificateManager.PfxPassword = "PfxPassword";
////Set Name(path) of the Root certificate file ////Set Name(path) of the Root certificate file
//proxyServer.PfxFilePath = @"C:\NameFolder\rootCert.pfx"; //proxyServer.CertificateManager.PfxFilePath = @"C:\NameFolder\rootCert.pfx";
////do you want Replace an existing Root certificate file(.pfx) if password is incorrect(RootCertificate=null)? yes====>true ////do you want Replace an existing Root certificate file(.pfx) if password is incorrect(RootCertificate=null)? yes====>true
//proxyServer.OverwritePfxFile = true; //proxyServer.CertificateManager.OverwritePfxFile = true;
////save all fake certificates in folder "crts"(will be created in proxy dll directory) ////save all fake certificates in folder "crts"(will be created in proxy dll directory)
////if create new Root certificate file(.pfx) ====> delete folder "crts" ////if create new Root certificate file(.pfx) ====> delete folder "crts"
//proxyServer.SaveFakeCertificates = true; //proxyServer.CertificateManager.SaveFakeCertificates = true;
//Trust Root Certificate
proxyServer.TrustRootCertificate = true;
proxyServer.TrustRootCertificateAsAdministrator = true;
proxyServer.ForwardToUpstreamGateway = true; proxyServer.ForwardToUpstreamGateway = true;
////if you need Load or Create Certificate now. ////// "true" if you need Enable===> Trust the RootCertificate used by this proxy server ////if you need Load or Create Certificate now. ////// "true" if you need Enable===> Trust the RootCertificate used by this proxy server
//proxyServer.EnsureRootCertificate(true); //proxyServer.CertificateManager.EnsureRootCertificate(true);
////or load directly certificate(As Administrator if need this) ////or load directly certificate(As Administrator if need this)
////and At the same time chose path and password ////and At the same time chose path and password
......
...@@ -52,11 +52,11 @@ Setup HTTP proxy: ...@@ -52,11 +52,11 @@ Setup HTTP proxy:
var proxyServer = new ProxyServer(); var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy //locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true; proxyServer.CertificateManager.TrustRootCertificate = true;
//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.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle;
proxyServer.BeforeRequest += OnRequest; proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse; proxyServer.BeforeResponse += OnResponse;
...@@ -66,17 +66,17 @@ proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection; ...@@ -66,17 +66,17 @@ proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{ {
//Exclude HTTPS addresses you don't want to proxy //You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown
//Useful for clients that use certificate pinning
//for example dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
//Use self-issued generic certificate on all HTTPS requests //Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each HTTPS-enabled domain //Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients //Useful when certificate trust is not required by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password") //GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
}; };
//Fired when a CONNECT request is received
explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect;
//An explicit endpoint is where the client knows about the existence of a proxy //An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner //So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint); proxyServer.AddEndPoint(explicitEndPoint);
...@@ -109,6 +109,7 @@ proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); ...@@ -109,6 +109,7 @@ proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
Console.Read(); Console.Read();
//Unsubscribe & Quit //Unsubscribe & Quit
explicitEndPoint.BeforeTunnelConnect -= OnBeforeTunnelConnect;
proxyServer.BeforeRequest -= OnRequest; proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse; proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation; proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
...@@ -125,6 +126,21 @@ Sample request and response event handlers ...@@ -125,6 +126,21 @@ Sample request and response event handlers
private IDictionary<Guid, string> requestBodyHistory private IDictionary<Guid, string> requestBodyHistory
= new ConcurrentDictionary<Guid, string>(); = new ConcurrentDictionary<Guid, string>();
private async Task<bool> OnBeforeTunnelConnect(string hostname)
{
if (hostname.Contains("dropbox.com"))
{
//Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example dropbox.com
return await Task.FromResult(true);
}
else
{
return await Task.FromResult(false);
}
}
public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
...@@ -152,7 +168,7 @@ public async Task OnRequest(object sender, SessionEventArgs e) ...@@ -152,7 +168,7 @@ public async Task OnRequest(object sender, SessionEventArgs e)
//Filter URL //Filter URL
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{ {
await e.Ok("<!DOCTYPE html>" + e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" + "<html><body><h1>" +
"Website Blocked" + "Website Blocked" +
"</h1>" + "</h1>" +
...@@ -163,7 +179,7 @@ public async Task OnRequest(object sender, SessionEventArgs e) ...@@ -163,7 +179,7 @@ public async Task OnRequest(object sender, SessionEventArgs e)
//Redirect example //Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{ {
await e.Redirect("https://www.paypal.com"); e.Redirect("https://www.paypal.com");
} }
} }
......
...@@ -51,7 +51,6 @@ namespace Titanium.Web.Proxy.IntegrationTests ...@@ -51,7 +51,6 @@ namespace Titanium.Web.Proxy.IntegrationTests
public ProxyTestController() public ProxyTestController()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
} }
public void StartProxy(int proxyPort) public void StartProxy(int proxyPort)
......
...@@ -15,28 +15,53 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -15,28 +15,53 @@ namespace Titanium.Web.Proxy.UnitTests
private readonly Random random = new Random(); private readonly Random random = new Random();
[TestMethod] [TestMethod]
public async Task Simple_Create_Certificate_Stress_Test() public async Task Simple_Create_Certificate_Test()
{ {
var tasks = new List<Task>(); var tasks = new List<Task>();
var mgr = new CertificateManager(new Lazy<Action<Exception>>(() => (e => { })).Value); var mgr = new CertificateManager(new Lazy<Action<Exception>>(() => (e => { })).Value);
mgr.ClearIdleCertificates(1); mgr.ClearIdleCertificates();
for (int i = 0; i < 1000; i++) foreach (string host in hostNames)
{ {
foreach (string host in hostNames) tasks.Add(Task.Run(async () =>
{ {
tasks.Add(Task.Run(async () =>
{
await Task.Delay(random.Next(0, 10) * 1000);
//get the connection //get the connection
var certificate = mgr.CreateCertificate(host, false); var certificate = await mgr.CreateCertificateAsync(host);
Assert.IsNotNull(certificate); Assert.IsNotNull(certificate);
})); }));
} }
await Task.WhenAll(tasks.ToArray());
mgr.StopClearIdleCertificates();
}
//uncomment this to compare WinCert maker performance with BC (BC takes more time for same test above)
//cannot run this test in build server since trusting the certificate won't happen successfully
//[TestMethod]
public async Task Simple_Create_Win_Certificate_Test()
{
var tasks = new List<Task>();
var mgr = new CertificateManager(new Lazy<Action<Exception>>(() => (e => { })).Value);
mgr.CreateRootCertificate(true);
mgr.TrustRootCertificate();
mgr.ClearIdleCertificates();
mgr.CertificateEngine = CertificateEngine.DefaultWindows;
foreach (string host in hostNames)
{
tasks.Add(Task.Run(async () =>
{
//get the connection
var certificate = await mgr.CreateCertificateAsync(host);
Assert.IsNotNull(certificate);
}));
} }
await Task.WhenAll(tasks.ToArray()); await Task.WhenAll(tasks.ToArray());
......
...@@ -531,7 +531,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -531,7 +531,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
/// <param name="html"></param> /// <param name="html"></param>
/// <param name="headers"></param> /// <param name="headers"></param>
public async Task Ok(string html, Dictionary<string, HttpHeader> headers = null) public void Ok(string html, Dictionary<string, HttpHeader> headers = null)
{ {
var response = new OkResponse(); var response = new OkResponse();
if (headers != null) if (headers != null)
...@@ -542,7 +542,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -542,7 +542,7 @@ namespace Titanium.Web.Proxy.EventArguments
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = response.Encoding.GetBytes(html ?? string.Empty); response.Body = response.Encoding.GetBytes(html ?? string.Empty);
await Respond(response); Respond(response);
} }
/// <summary> /// <summary>
...@@ -552,14 +552,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -552,14 +552,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
/// <param name="result"></param> /// <param name="result"></param>
/// <param name="headers"></param> /// <param name="headers"></param>
public async Task Ok(byte[] result, Dictionary<string, HttpHeader> headers = null) public void Ok(byte[] result, Dictionary<string, HttpHeader> headers = null)
{ {
var response = new OkResponse(); var response = new OkResponse();
response.Headers.AddHeaders(headers); response.Headers.AddHeaders(headers);
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Body = result; response.Body = result;
await Respond(response); Respond(response);
} }
/// <summary> /// <summary>
...@@ -572,14 +572,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -572,14 +572,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="status"></param> /// <param name="status"></param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
public async Task GenericResponse(string html, HttpStatusCode status, Dictionary<string, HttpHeader> headers = null) public void GenericResponse(string html, HttpStatusCode status, Dictionary<string, HttpHeader> headers = null)
{ {
var response = new GenericResponse(status); var response = new GenericResponse(status);
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeaders(headers); response.Headers.AddHeaders(headers);
response.Body = response.Encoding.GetBytes(html ?? string.Empty); response.Body = response.Encoding.GetBytes(html ?? string.Empty);
await Respond(response); Respond(response);
} }
/// <summary> /// <summary>
...@@ -592,14 +592,14 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -592,14 +592,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="status"></param> /// <param name="status"></param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
public async Task GenericResponse(byte[] result, HttpStatusCode status, Dictionary<string, HttpHeader> headers) public void GenericResponse(byte[] result, HttpStatusCode status, Dictionary<string, HttpHeader> headers)
{ {
var response = new GenericResponse(status); var response = new GenericResponse(status);
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeaders(headers); response.Headers.AddHeaders(headers);
response.Body = result; response.Body = result;
await Respond(response); Respond(response);
} }
/// <summary> /// <summary>
...@@ -607,18 +607,18 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -607,18 +607,18 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
/// <param name="url"></param> /// <param name="url"></param>
/// <returns></returns> /// <returns></returns>
public async Task Redirect(string url) public void Redirect(string url)
{ {
var response = new RedirectResponse(); var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion; response.HttpVersion = WebSession.Request.HttpVersion;
response.Headers.AddHeader(KnownHeaders.Location, url); response.Headers.AddHeader(KnownHeaders.Location, url);
response.Body = emptyData; response.Body = emptyData;
await Respond(response); Respond(response);
} }
/// a generic responder method /// a generic responder method
public async Task Respond(Response response) public void Respond(Response response)
{ {
if (WebSession.Request.RequestLocked) if (WebSession.Request.RequestLocked)
{ {
......
...@@ -33,29 +33,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -33,29 +33,6 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
public sealed class CertificateManager : IDisposable public sealed class CertificateManager : IDisposable
{ {
internal CertificateEngine Engine
{
get => engine;
set
{
//For Mono (or Non-Windows) only Bouncy Castle is supported
if (!RunTime.IsWindows || RunTime.IsRunningOnMono)
{
value = CertificateEngine.BouncyCastle;
}
if (value != engine)
{
certEngine = null;
engine = value;
}
if (certEngine == null)
{
certEngine = engine == CertificateEngine.BouncyCastle ? (ICertificateMaker)new BCCertificateMaker(exceptionFunc) : new WinCertificateMaker(exceptionFunc);
}
}
}
private const string defaultRootCertificateIssuer = "Titanium"; private const string defaultRootCertificateIssuer = "Titanium";
...@@ -75,46 +52,107 @@ namespace Titanium.Web.Proxy.Network ...@@ -75,46 +52,107 @@ namespace Titanium.Web.Proxy.Network
private X509Certificate2 rootCertificate; private X509Certificate2 rootCertificate;
internal bool trustRootCertificate { get; set; } = false; /// <summary>
/// Cache dictionary
/// </summary>
private readonly ConcurrentDictionary<string, CachedCertificate> certificateCache;
private readonly ConcurrentDictionary<string, Task<X509Certificate2>> pendingCertificateCreationTasks;
private readonly Action<Exception> exceptionFunc;
/// <summary>
/// Is the root certificate used by this proxy is valid?
/// </summary>
internal bool CertValidated => RootCertificate != null;
internal bool OverwritePfXFile { get; set; } = true; /// <summary>
/// Trust the RootCertificate used by this proxy server
/// 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
/// </summary>
internal bool TrustRoot { get; set; } = false;
internal string PfxPassword { get; set; } = string.Empty; /// <summary>
/// Needs elevated permission. Works only on Windows.
/// <para>Puts the certificate to the local machine's certificate store.</para>
/// <para>Certutil.exe is a command-line program that is installed as part of Certificate Services</para>
/// </summary>
internal bool TrustRootAsAdministrator { get; set; } = false;
internal string PfxFilePath { get; set; } = string.Empty; /// <summary>
/// Select Certificate Engine
/// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default
/// </summary>
public CertificateEngine CertificateEngine
{
get => engine;
set
{
//For Mono (or Non-Windows) only Bouncy Castle is supported
if (!RunTime.IsWindows || RunTime.IsRunningOnMono)
{
value = CertificateEngine.BouncyCastle;
}
internal X509KeyStorageFlags StorageFlag { get; set; } = X509KeyStorageFlags.Exportable; if (value != engine)
{
certEngine = null;
engine = value;
}
internal bool SaveFakeCertificates { get; set; } = false; if (certEngine == null)
{
certEngine = engine == CertificateEngine.BouncyCastle ? (ICertificateMaker)new BCCertificateMaker(exceptionFunc) : new WinCertificateMaker(exceptionFunc);
}
}
}
/// <summary> /// <summary>
/// Cache dictionary /// Password of the Root certificate file
/// <para>Set a password for the .pfx file</para>
/// </summary> /// </summary>
private readonly IDictionary<string, CachedCertificate> certificateCache; public string PfxPassword { get; set; } = string.Empty;
private readonly Action<Exception> exceptionFunc; /// <summary>
/// Name(path) of the Root certificate file
/// <para>Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as "rootCert.pfx" (and will be saved in proxy dll directory)</para>
/// </summary>
public string PfxFilePath { get; set; } = string.Empty;
internal string Issuer /// <summary>
/// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set)
/// </summary>
public string RootCertificateIssuerName
{ {
get => issuer ?? defaultRootCertificateIssuer; get => issuer ?? defaultRootCertificateIssuer;
set set
{ {
issuer = value; issuer = value;
//ClearRootCertificate();
} }
} }
internal string RootCertificateName /// <summary>
/// Name of the root certificate
/// (This is valid only when RootCertificate property is not set)
/// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate will be stored in proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx"
/// </summary>
public string RootCertificateName
{ {
get => rootCertificateName ?? defaultRootRootCertificateName; get => rootCertificateName ?? defaultRootRootCertificateName;
set set
{ {
rootCertificateName = value; rootCertificateName = value;
//ClearRootCertificate();
} }
} }
internal X509Certificate2 RootCertificate /// <summary>
/// The root certificate
/// </summary>
public X509Certificate2 RootCertificate
{ {
get => rootCertificate; get => rootCertificate;
set set
...@@ -125,23 +163,45 @@ namespace Titanium.Web.Proxy.Network ...@@ -125,23 +163,45 @@ namespace Titanium.Web.Proxy.Network
} }
/// <summary> /// <summary>
/// Is the root certificate used by this proxy is valid? /// Save all fake certificates in folder "crts"(will be created in proxy dll directory)
/// <para>for can load the certificate and not make new certificate every time </para>
/// </summary> /// </summary>
internal bool CertValidated => RootCertificate != null; public bool SaveFakeCertificates { get; set; } = false;
/// <summary>
/// Overwrite Root certificate file
/// <para>true : replace an existing .pfx file if password is incorect or if RootCertificate = null</para>
/// </summary>
public bool OverwritePfxFile { get; set; } = true;
/// <summary>
/// Minutes certificates should be kept in cache when not used
/// </summary>
public int CertificateCacheTimeOutMinutes { get; set; } = 60;
/// <summary>
/// Adjust behaviour when certificates are saved to filesystem
/// </summary>
public X509KeyStorageFlags StorageFlag { get; set; } = X509KeyStorageFlags.Exportable;
internal CertificateManager(Action<Exception> exceptionFunc) internal CertificateManager(Action<Exception> exceptionFunc)
{ {
this.exceptionFunc = exceptionFunc; this.exceptionFunc = exceptionFunc;
Engine = CertificateEngine.BouncyCastle; if(RunTime.IsWindows)
{
//this is faster in Windows based on tests (see unit test project CertificateManagerTests.cs)
CertificateEngine = CertificateEngine.DefaultWindows;
}
else
{
CertificateEngine = CertificateEngine.BouncyCastle;
}
certificateCache = new ConcurrentDictionary<string, CachedCertificate>(); certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
pendingCertificateCreationTasks = new ConcurrentDictionary<string, Task<X509Certificate2>>();
} }
public void ClearRootCertificate()
{
certificateCache.Clear();
rootCertificate = null;
}
private string GetRootCertificateDirectory() private string GetRootCertificateDirectory()
{ {
...@@ -155,12 +215,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -155,12 +215,12 @@ namespace Titanium.Web.Proxy.Network
string path = Path.GetDirectoryName(assemblyLocation); string path = Path.GetDirectoryName(assemblyLocation);
if (null == path) if (null == path)
throw new NullReferenceException(); throw new NullReferenceException();
return path; return path;
} }
private string GetCertPath() private string GetCertificatePath()
{ {
string path = GetRootCertificateDirectory(); string path = GetRootCertificateDirectory();
...@@ -176,7 +236,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -176,7 +236,7 @@ namespace Titanium.Web.Proxy.Network
private string GetRootCertificatePath() private string GetRootCertificatePath()
{ {
string path = GetRootCertificateDirectory(); string path = GetRootCertificateDirectory();
string fileName = PfxFilePath; string fileName = PfxFilePath;
if (fileName == string.Empty) if (fileName == string.Empty)
{ {
...@@ -187,6 +247,283 @@ namespace Titanium.Web.Proxy.Network ...@@ -187,6 +247,283 @@ namespace Titanium.Web.Proxy.Network
return fileName; return fileName;
} }
private bool FindRootCertificate(StoreLocation storeLocation)
{
string value = $"{RootCertificate.Issuer}";
return FindCertificates(StoreName.Root, storeLocation, value).Count > 0;
}
private X509Certificate2Collection FindCertificates(StoreName storeName, StoreLocation storeLocation, string findValue)
{
var x509Store = new X509Store(storeName, storeLocation);
try
{
x509Store.Open(OpenFlags.OpenExistingOnly);
return x509Store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, findValue, false);
}
finally
{
x509Store.Close();
}
}
private X509Certificate2 MakeCertificate(string certificateName, bool isRootCertificate)
{
if (!isRootCertificate && RootCertificate == null)
{
CreateRootCertificate();
}
return certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate);
}
/// <summary>
/// Make current machine trust the Root Certificate used by this proxy
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
private void TrustRootCertificate(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
exceptionFunc(
new Exception("Could not set root certificate"
+ " as system proxy since it is null or empty."));
return;
}
var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
{
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
x509RootStore.Add(RootCertificate);
x509PersonalStore.Add(RootCertificate);
}
catch (Exception e)
{
exceptionFunc(
new Exception("Failed to make system trust root certificate "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
{
x509RootStore.Close();
x509PersonalStore.Close();
}
}
/// <summary>
/// Remove the Root Certificate trust
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
private void RemoveTrustedRootCertificate(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
exceptionFunc(
new Exception("Could not set root certificate"
+ " as system proxy since it is null or empty."));
return;
}
var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
{
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
x509RootStore.Remove(RootCertificate);
x509PersonalStore.Remove(RootCertificate);
}
catch (Exception e)
{
exceptionFunc(
new Exception("Failed to remove root certificate trust "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
{
x509RootStore.Close();
x509PersonalStore.Close();
}
}
/// <summary>
/// Create an SSL certificate
/// </summary>
/// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param>
/// <returns></returns>
internal X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{
X509Certificate2 certificate = null;
try
{
if (!isRootCertificate && SaveFakeCertificates)
{
string path = GetCertificatePath();
string subjectName = BCCertificateMaker.CNRemoverRegex.Replace(certificateName, string.Empty);
subjectName = subjectName.Replace("*", "$x$");
subjectName = Path.Combine(path, subjectName + ".pfx");
if (!File.Exists(subjectName))
{
certificate = MakeCertificate(certificateName, isRootCertificate);
File.WriteAllBytes(subjectName, certificate.Export(X509ContentType.Pkcs12));
}
else
{
try
{
certificate = new X509Certificate2(subjectName, string.Empty, StorageFlag);
}
catch /* (Exception e)*/
{
certificate = MakeCertificate(certificateName, isRootCertificate);
}
}
}
else
{
certificate = MakeCertificate(certificateName, isRootCertificate);
}
}
catch (Exception e)
{
exceptionFunc(e);
}
return certificate;
}
/// <summary>
/// Create an SSL certificate async
/// </summary>
/// <param name="certificateName"></param>
/// <returns></returns>
internal async Task<X509Certificate2> CreateCertificateAsync(string certificateName)
{
//check in cache first
CachedCertificate cached;
if (certificateCache.TryGetValue(certificateName, out cached))
{
cached.LastAccess = DateTime.Now;
return cached.Certificate;
}
//handle burst requests with same certificate name
//by checking for existing task for same certificate name
Task<X509Certificate2> task;
if (pendingCertificateCreationTasks.TryGetValue(certificateName, out task))
{
//certificate already added to cache
//just return the result here
return await task;
}
//run certificate creation task & add it to pending tasks
task = Task.Run(() =>
{
var result = CreateCertificate(certificateName, false);
if (result != null)
{
//this is ConcurrentDictionary
//if key exists it will silently handle; no need for locking
certificateCache.TryAdd(certificateName, new CachedCertificate
{
Certificate = result
});
}
return result;
});
pendingCertificateCreationTasks.TryAdd(certificateName, task);
//cleanup pending tasks & return result
var certificate = await task;
pendingCertificateCreationTasks.TryRemove(certificateName, out task);
return certificate;
}
/// <summary>
/// Stops the certificate cache clear process
/// </summary>
internal void StopClearIdleCertificates()
{
clearCertificates = false;
}
/// <summary>
/// A method to clear outdated certificates
/// </summary>
internal async void ClearIdleCertificates()
{
clearCertificates = true;
while (clearCertificates)
{
var cutOff = DateTime.Now.AddMinutes(-1 * CertificateCacheTimeOutMinutes);
var outdated = certificateCache.Where(x => x.Value.LastAccess < cutOff).ToList();
CachedCertificate removed;
foreach (var cache in outdated)
certificateCache.TryRemove(cache.Key, out removed);
//after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60);
}
}
/// <summary>
/// Load or Create Certificate : after "Test Is the root certificate used by this proxy is valid?"
/// <param name="trustRootCertificate">"Make current machine trust the Root Certificate used by this proxy" ==> True or False</param>
/// </summary>
public void EnsureRootCertificate(bool trustRootCertificate, bool trustRootCertificateAsAdmin)
{
this.TrustRoot = trustRootCertificate;
this.TrustRootAsAdministrator = trustRootCertificateAsAdmin;
EnsureRootCertificate();
}
/// <summary>
/// Create/load root if required and ensure that the root certificate is trusted.
/// </summary>
public void EnsureRootCertificate()
{
if (!CertValidated)
{
CreateRootCertificate();
if (TrustRoot)
{
TrustRootCertificate();
}
if (TrustRootAsAdministrator)
{
TrustRootCertificateAsAdmin();
}
}
}
public void ClearRootCertificate()
{
certificateCache.Clear();
rootCertificate = null;
}
public X509Certificate2 LoadRootCertificate() public X509Certificate2 LoadRootCertificate()
{ {
string fileName = GetRootCertificatePath(); string fileName = GetRootCertificatePath();
...@@ -195,7 +532,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -195,7 +532,7 @@ namespace Titanium.Web.Proxy.Network
{ {
return null; return null;
} }
try try
{ {
return new X509Certificate2(fileName, PfxPassword, StorageFlag); return new X509Certificate2(fileName, PfxPassword, StorageFlag);
...@@ -214,7 +551,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -214,7 +551,7 @@ namespace Titanium.Web.Proxy.Network
/// <returns> /// <returns>
/// true if succeeded, else false /// true if succeeded, else false
/// </returns> /// </returns>
public bool CreateTrustedRootCertificate(bool persistToFile = true) public bool CreateRootCertificate(bool persistToFile = true)
{ {
if (persistToFile && RootCertificate == null) if (persistToFile && RootCertificate == null)
{ {
...@@ -226,7 +563,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -226,7 +563,7 @@ namespace Titanium.Web.Proxy.Network
return true; return true;
} }
if (!OverwritePfXFile && pfxFileExists) if (!OverwritePfxFile && pfxFileExists)
{ {
return false; return false;
} }
...@@ -246,14 +583,14 @@ namespace Titanium.Web.Proxy.Network ...@@ -246,14 +583,14 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
Directory.Delete(GetCertPath(), true); Directory.Delete(GetCertificatePath(), true);
} }
catch catch
{ {
// ignore // ignore
} }
string fileName = GetRootCertificatePath(); string fileName = GetRootCertificatePath();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword)); File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword));
} }
catch (Exception e) catch (Exception e)
...@@ -279,7 +616,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -279,7 +616,7 @@ namespace Titanium.Web.Proxy.Network
{ {
PfxFilePath = pfxFilePath; PfxFilePath = pfxFilePath;
PfxPassword = password; PfxPassword = password;
OverwritePfXFile = overwritePfXFile; OverwritePfxFile = overwritePfXFile;
StorageFlag = storageFlag; StorageFlag = storageFlag;
RootCertificate = LoadRootCertificate(); RootCertificate = LoadRootCertificate();
...@@ -304,7 +641,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -304,7 +641,7 @@ namespace Titanium.Web.Proxy.Network
/// Needs elevated permission. Works only on Windows. /// Needs elevated permission. Works only on Windows.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public bool TrustRootCertificateAsAdministrator() public bool TrustRootCertificateAsAdmin()
{ {
if (!RunTime.IsWindows || RunTime.IsRunningOnMono) if (!RunTime.IsWindows || RunTime.IsRunningOnMono)
{ {
...@@ -317,7 +654,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -317,7 +654,7 @@ namespace Titanium.Web.Proxy.Network
var info = new ProcessStartInfo var info = new ProcessStartInfo
{ {
FileName = "certutil.exe", FileName = "certutil.exe",
Arguments = "-importPFX -p \""+ PfxPassword + "\" -f \"" + fileName + "\"", Arguments = "-importPFX -p \"" + PfxPassword + "\" -f \"" + fileName + "\"",
CreateNoWindow = true, CreateNoWindow = true,
UseShellExecute = true, UseShellExecute = true,
Verb = "runas", Verb = "runas",
...@@ -347,13 +684,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -347,13 +684,13 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Removes the trusted certificates. /// Removes the trusted certificates.
/// </summary> /// </summary>
public void RemoveTrustedRootCertificates() public void RemoveTrustedRootCertificate()
{ {
//current user //current user
RemoveTrustedRootCertificates(StoreLocation.CurrentUser); RemoveTrustedRootCertificate(StoreLocation.CurrentUser);
//current system //current system
RemoveTrustedRootCertificates(StoreLocation.LocalMachine); RemoveTrustedRootCertificate(StoreLocation.LocalMachine);
} }
/// <summary> /// <summary>
...@@ -361,7 +698,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -361,7 +698,7 @@ namespace Titanium.Web.Proxy.Network
/// Needs elevated permission. Works only on Windows. /// Needs elevated permission. Works only on Windows.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public bool RemoveTrustedRootCertificatesAsAdministrator() public bool RemoveTrustedRootCertificateAsAdmin()
{ {
if (!RunTime.IsWindows || RunTime.IsRunningOnMono) if (!RunTime.IsWindows || RunTime.IsRunningOnMono)
{ {
...@@ -412,221 +749,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -412,221 +749,7 @@ namespace Titanium.Web.Proxy.Network
return FindRootCertificate(StoreLocation.LocalMachine); return FindRootCertificate(StoreLocation.LocalMachine);
} }
private bool FindRootCertificate(StoreLocation storeLocation)
{
string value = $"{RootCertificate.Issuer}";
return FindCertificates(StoreName.Root, storeLocation, value).Count > 0;
}
private X509Certificate2Collection FindCertificates(StoreName storeName, StoreLocation storeLocation, string findValue)
{
var x509Store = new X509Store(storeName, storeLocation);
try
{
x509Store.Open(OpenFlags.OpenExistingOnly);
return x509Store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, findValue, false);
}
finally
{
x509Store.Close();
}
}
private X509Certificate2 MakeCertificate(string certificateName, bool isRootCertificate)
{
if (!isRootCertificate && RootCertificate == null)
{
CreateTrustedRootCertificate();
}
return certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate);
}
/// <summary>
/// Create an SSL certificate
/// </summary>
/// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param>
/// <returns></returns>
internal X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{
if (certificateCache.ContainsKey(certificateName))
{
var cached = certificateCache[certificateName];
cached.LastAccess = DateTime.Now;
return cached.Certificate;
}
X509Certificate2 certificate = null;
lock (string.Intern(certificateName))
{
if (certificateCache.ContainsKey(certificateName) == false)
{
try
{
if (!isRootCertificate && SaveFakeCertificates)
{
string path = GetCertPath();
string subjectName = BCCertificateMaker.CNRemoverRegex.Replace(certificateName, string.Empty);
subjectName = subjectName.Replace("*", "$x$");
subjectName = Path.Combine(path, subjectName + ".pfx");
if (!File.Exists(subjectName))
{
certificate = MakeCertificate(certificateName, isRootCertificate);
File.WriteAllBytes(subjectName, certificate.Export(X509ContentType.Pkcs12));
}
else
{
try
{
certificate = new X509Certificate2(subjectName, string.Empty, StorageFlag);
}
catch /* (Exception e)*/
{
certificate = MakeCertificate(certificateName, isRootCertificate);
}
}
}
else
{
certificate = MakeCertificate(certificateName, isRootCertificate);
}
}
catch (Exception e)
{
exceptionFunc(e);
}
if (certificate != null && !certificateCache.ContainsKey(certificateName))
{
certificateCache.Add(certificateName, new CachedCertificate
{
Certificate = certificate
});
}
}
else
{
if (certificateCache.ContainsKey(certificateName))
{
var cached = certificateCache[certificateName];
cached.LastAccess = DateTime.Now;
return cached.Certificate;
}
}
}
return certificate;
}
/// <summary>
/// Stops the certificate cache clear process
/// </summary>
internal void StopClearIdleCertificates()
{
clearCertificates = false;
}
/// <summary>
/// A method to clear outdated certificates
/// </summary>
internal async void ClearIdleCertificates(int certificateCacheTimeOutMinutes)
{
clearCertificates = true;
while (clearCertificates)
{
var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes);
var outdated = certificateCache.Where(x => x.Value.LastAccess < cutOff).ToList();
foreach (var cache in outdated)
certificateCache.Remove(cache.Key);
//after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60);
}
}
/// <summary>
/// Make current machine trust the Root Certificate used by this proxy
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
private void TrustRootCertificate(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
exceptionFunc(
new Exception("Could not set root certificate"
+ " as system proxy since it is null or empty."));
return;
}
var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
{
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
x509RootStore.Add(RootCertificate);
x509PersonalStore.Add(RootCertificate);
}
catch (Exception e)
{
exceptionFunc(
new Exception("Failed to make system trust root certificate "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
{
x509RootStore.Close();
x509PersonalStore.Close();
}
}
/// <summary>
/// Remove the Root Certificate trust
/// </summary>
/// <param name="storeLocation"></param>
/// <returns></returns>
private void RemoveTrustedRootCertificates(StoreLocation storeLocation)
{
if (RootCertificate == null)
{
exceptionFunc(
new Exception("Could not set root certificate"
+ " as system proxy since it is null or empty."));
return;
}
var x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
{
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
x509RootStore.Remove(RootCertificate);
x509PersonalStore.Remove(RootCertificate);
}
catch (Exception e)
{
exceptionFunc(
new Exception("Failed to remove root certificate trust "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
{
x509RootStore.Close();
x509PersonalStore.Close();
}
}
/// <summary> /// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
......
using StreamExtended.Network;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
...@@ -37,13 +35,14 @@ namespace Titanium.Web.Proxy ...@@ -37,13 +35,14 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private int serverConnectionCount; private int serverConnectionCount;
private X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable;
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// A object that creates tcp connection to server
/// </summary> /// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; } private TcpConnectionFactory tcpConnectionFactory { get; }
/// <summary>
/// Manaage upstream proxy detection
/// </summary>
private WinHttpWebProxyFinder systemProxyResolver; private WinHttpWebProxyFinder systemProxyResolver;
/// <summary> /// <summary>
...@@ -80,44 +79,6 @@ namespace Titanium.Web.Proxy ...@@ -80,44 +79,6 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public bool EnableWinAuth { get; set; } public bool EnableWinAuth { get; set; }
/// <summary>
/// Trust the RootCertificate used by this proxy server
/// 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
/// </summary>
public bool TrustRootCertificate
{
get => CertificateManager.trustRootCertificate;
set => CertificateManager.trustRootCertificate = value;
}
/// <summary>
/// Needs elevated permission. Works only on Windows.
/// <para>Puts the certificate to the local machine's certificate store.</para>
/// <para>Certutil.exe is a command-line program that is installed as part of Certificate Services</para>
/// </summary>
public bool TrustRootCertificateAsAdministrator { get; set; } = false;
/// <summary>
/// Save all fake certificates in folder "crts"(will be created in proxy dll directory)
/// <para>for can load the certificate and not make new certificate every time </para>
/// </summary>
public bool SaveFakeCertificates
{
get => CertificateManager.SaveFakeCertificates;
set => CertificateManager.SaveFakeCertificates = value;
}
/// <summary>
/// Overwrite Root certificate file
/// <para>true : replace an existing .pfx file if password is incorect or if RootCertificate = null</para>
/// </summary>
public bool OverwritePfxFile
{
get => CertificateManager.OverwritePfXFile;
set => CertificateManager.OverwritePfXFile = value;
}
/// <summary> /// <summary>
/// Should we check for certificare revocation during SSL authentication to servers /// Should we check for certificare revocation during SSL authentication to servers
/// Note: If enabled can reduce performance (Default disabled) /// Note: If enabled can reduce performance (Default disabled)
...@@ -135,15 +96,10 @@ namespace Titanium.Web.Proxy ...@@ -135,15 +96,10 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public int BufferSize { get; set; } = 8192; public int BufferSize { get; set; } = 8192;
/// <summary>
/// Minutes certificates should be kept in cache when not used
/// </summary>
public int CertificateCacheTimeOutMinutes { get; set; }
/// <summary> /// <summary>
/// Seconds client/server connection are to be kept alive when waiting for read/write to complete /// Seconds client/server connection are to be kept alive when waiting for read/write to complete
/// </summary> /// </summary>
public int ConnectionTimeOutSeconds { get; set; } public int ConnectionTimeOutSeconds { get; set; }
/// <summary> /// <summary>
/// Total number of active client connections /// Total number of active client connections
...@@ -155,56 +111,12 @@ namespace Titanium.Web.Proxy ...@@ -155,56 +111,12 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public int ServerConnectionCount => serverConnectionCount; public int ServerConnectionCount => serverConnectionCount;
/// <summary>
/// Name of the root certificate issuer
/// (This is valid only when RootCertificate property is not set)
/// </summary>
public string RootCertificateIssuerName
{
get => CertificateManager.Issuer;
set => CertificateManager.Issuer = value;
}
/// <summary>
/// Name of the root certificate
/// (This is valid only when RootCertificate property is not set)
/// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate will be stored in proxy exe directory with the private key
/// Root certificate file will be named as "rootCert.pfx"
/// </summary>
public string RootCertificateName
{
get => CertificateManager.RootCertificateName;
set => CertificateManager.RootCertificateName = value;
}
/// <summary> /// <summary>
/// Realm used during Proxy Basic Authentication /// Realm used during Proxy Basic Authentication
/// </summary> /// </summary>
public string ProxyRealm { get; set; } = "TitaniumProxy"; public string ProxyRealm { get; set; } = "TitaniumProxy";
/// <summary>
/// Password of the Root certificate file
/// <para>Set a password for the .pfx file</para>
/// </summary>
public string PfxPassword
{
get => CertificateManager.PfxPassword;
set => CertificateManager.PfxPassword = value;
}
/// <summary>
/// Name(path) of the Root certificate file
/// <para>Set the name(path) of the .pfx file. If it is string.Empty Root certificate file will be named as "rootCert.pfx" (and will be saved in proxy dll directory)</para>
/// </summary>
public string PfxFilePath
{
get => CertificateManager.PfxFilePath;
set => CertificateManager.PfxFilePath = value;
}
/// <summary> /// <summary>
/// List of supported Ssl versions /// List of supported Ssl versions
/// </summary> /// </summary>
...@@ -214,35 +126,6 @@ namespace Titanium.Web.Proxy ...@@ -214,35 +126,6 @@ namespace Titanium.Web.Proxy
#endif #endif
SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
/// <summary>
/// The root certificate
/// </summary>
public X509Certificate2 RootCertificate
{
get => CertificateManager.RootCertificate;
set => CertificateManager.RootCertificate = value;
}
public X509KeyStorageFlags StorageFlag
{
get => storageFlag;
set
{
storageFlag = value;
CertificateManager.StorageFlag = storageFlag;
}
}
/// <summary>
/// Select Certificate Engine
/// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default
/// </summary>
public CertificateEngine CertificateEngine
{
get => CertificateManager.Engine;
set => CertificateManager.Engine = value;
}
/// <summary> /// <summary>
/// Manages certificates used by this proxy /// Manages certificates used by this proxy
...@@ -322,24 +205,37 @@ namespace Titanium.Web.Proxy ...@@ -322,24 +205,37 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; } public Func<string, string, Task<bool>> AuthenticateUserFunc { get; set; }
/// <summary>
/// Constructor
/// </summary>
public ProxyServer() : this(null, null, true, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public ProxyServer(bool trustRoot) : this(null, null, trustRoot, false)
{
}
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
public ProxyServer() : this(null, null) public ProxyServer(bool trustRoot, bool trustRootAsAdmin) : this(null, null, trustRoot, trustRootAsAdmin)
{ {
} }
/// <summary> /// <summary>
/// Constructor. /// Constructor.
/// </summary> /// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param> /// <param name="rootCertificateName">Name of root certificate.</param>
/// <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, bool trustRootCertificate, bool trustRootCertificateAsAdmin)
{ {
//default values //default values
ConnectionTimeOutSeconds = 30; ConnectionTimeOutSeconds = 30;
CertificateCacheTimeOutMinutes = 60;
ProxyEndPoints = new List<ProxyEndPoint>(); ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory(); tcpConnectionFactory = new TcpConnectionFactory();
...@@ -349,14 +245,17 @@ namespace Titanium.Web.Proxy ...@@ -349,14 +245,17 @@ namespace Titanium.Web.Proxy
} }
CertificateManager = new CertificateManager(ExceptionFunc); CertificateManager = new CertificateManager(ExceptionFunc);
CertificateManager.TrustRoot = trustRootCertificate;
CertificateManager.TrustRootAsAdministrator = trustRootCertificateAsAdmin;
if (rootCertificateName != null) if (rootCertificateName != null)
{ {
RootCertificateName = rootCertificateName; CertificateManager.RootCertificateName = rootCertificateName;
} }
if (rootCertificateIssuerName != null) if (rootCertificateIssuerName != null)
{ {
RootCertificateIssuerName = rootCertificateIssuerName; CertificateManager.RootCertificateIssuerName = rootCertificateIssuerName;
} }
} }
...@@ -441,7 +340,7 @@ namespace Titanium.Web.Proxy ...@@ -441,7 +340,7 @@ namespace Titanium.Web.Proxy
throw new Exception("Endpoint do not support Https connections"); throw new Exception("Endpoint do not support Https connections");
} }
EnsureRootCertificate(); CertificateManager.EnsureRootCertificate();
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (!CertificateManager.CertValidated) if (!CertificateManager.CertValidated)
...@@ -554,7 +453,7 @@ namespace Titanium.Web.Proxy ...@@ -554,7 +453,7 @@ namespace Titanium.Web.Proxy
if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null)) if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null))
{ {
EnsureRootCertificate(); CertificateManager.EnsureRootCertificate();
} }
//clear any system proxy settings which is pointing to our own endpoint (causing a cycle) //clear any system proxy settings which is pointing to our own endpoint (causing a cycle)
...@@ -599,7 +498,7 @@ namespace Titanium.Web.Proxy ...@@ -599,7 +498,7 @@ namespace Titanium.Web.Proxy
Listen(endPoint); Listen(endPoint);
} }
CertificateManager.ClearIdleCertificates(CertificateCacheTimeOutMinutes); CertificateManager.ClearIdleCertificates();
if (RunTime.IsWindows && !RunTime.IsRunningOnMono) if (RunTime.IsWindows && !RunTime.IsRunningOnMono)
{ {
...@@ -708,34 +607,6 @@ namespace Titanium.Web.Proxy ...@@ -708,34 +607,6 @@ namespace Titanium.Web.Proxy
return Task.FromResult(proxy); return Task.FromResult(proxy);
} }
/// <summary>
/// Load or Create Certificate : after "Test Is the root certificate used by this proxy is valid?"
/// <param name="trustRootCertificate">"Make current machine trust the Root Certificate used by this proxy" ==> True or False</param>
/// </summary>
public void EnsureRootCertificate(bool trustRootCertificate)
{
TrustRootCertificate = trustRootCertificate;
EnsureRootCertificate();
}
public void EnsureRootCertificate()
{
if (!CertificateManager.CertValidated)
{
CertificateManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
CertificateManager.TrustRootCertificate();
}
if (TrustRootCertificateAsAdministrator)
{
CertificateManager.TrustRootCertificateAsAdministrator();
}
}
}
/// <summary> /// <summary>
/// When a connection is received from client act /// When a connection is received from client act
/// </summary> /// </summary>
......
...@@ -132,7 +132,7 @@ namespace Titanium.Web.Proxy ...@@ -132,7 +132,7 @@ namespace Titanium.Web.Proxy
string certName = HttpHelper.GetWildCardDomainName(connectHostname); string certName = HttpHelper.GetWildCardDomainName(connectHostname);
var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false); var certificate = endPoint.GenericCertificate ?? await CertificateManager.CreateCertificateAsync(certName);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false); await sslStream.AuthenticateAsServerAsync(certificate, false, SupportedSslProtocols, false);
...@@ -252,7 +252,7 @@ namespace Titanium.Web.Proxy ...@@ -252,7 +252,7 @@ namespace Titanium.Web.Proxy
string sniHostName = clientHelloInfo.GetServerName(); string sniHostName = clientHelloInfo.GetServerName();
string certName = HttpHelper.GetWildCardDomainName(sniHostName ?? endPoint.GenericCertificateName); string certName = HttpHelper.GetWildCardDomainName(sniHostName ?? endPoint.GenericCertificateName);
var certificate = CertificateManager.CreateCertificate(certName, false); var certificate = await CertificateManager.CreateCertificateAsync(certName);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false); await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
......
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks> <TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
<RootNamespace>Titanium.Web.Proxy</RootNamespace> <RootNamespace>Titanium.Web.Proxy</RootNamespace>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<SignAssembly>True</SignAssembly> <SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
<DelaySign>False</DelaySign> <DelaySign>False</DelaySign>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>7.1</LangVersion> <LangVersion>7.1</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.3" /> <PackageReference Include="Portable.BouncyCastle" Version="1.8.1.4" />
<PackageReference Include="StreamExtended" Version="1.0.135-beta" /> <PackageReference Include="StreamExtended" Version="1.0.135-beta" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Win32.Registry"> <PackageReference Include="Microsoft.Win32.Registry">
<Version>4.4.0</Version> <Version>4.4.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="System.Security.Principal.Windows"> <PackageReference Include="System.Security.Principal.Windows">
<Version>4.4.1</Version> <Version>4.4.1</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
</Project> </Project>
\ No newline at end of file
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<tags></tags> <tags></tags>
<dependencies> <dependencies>
<dependency id="StreamExtended" version="1.0.110-beta" /> <dependency id="StreamExtended" version="1.0.110-beta" />
<dependency id="Portable.BouncyCastle" version="1.8.1.3" /> <dependency id="Portable.BouncyCastle" version="1.8.1.4" />
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Portable.BouncyCastle" version="1.8.1.3" targetFramework="net45" /> <package id="Portable.BouncyCastle" version="1.8.1.4" targetFramework="net45" />
<package id="StreamExtended" version="1.0.110-beta" targetFramework="net45" /> <package id="StreamExtended" version="1.0.110-beta" targetFramework="net45" />
</packages> </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