Unverified Commit 63ce4b3d authored by honfika's avatar honfika Committed by GitHub

Merge pull request #376 from justcoding121/develop

Beta
parents e817f10c f11b6b60
...@@ -170,18 +170,18 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -170,18 +170,18 @@ namespace Titanium.Web.Proxy.Examples.Basic
//requestBodyHistory[e.Id] = bodyString; //requestBodyHistory[e.Id] = bodyString;
} }
////To cancel a request with a custom HTML content //To cancel a request with a custom HTML content
////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>" + await e.Ok("<!DOCTYPE html>" +
// "<html><body><h1>" + "<html><body><h1>" +
// "Website Blocked" + "Website Blocked" +
// "</h1>" + "</h1>" +
// "<p>Blocked by titanium web proxy.</p>" + "<p>Blocked by titanium web proxy.</p>" +
// "</body>" + "</body>" +
// "</html>"); "</html>");
//} }
////Redirect example ////Redirect example
//if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) //if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
...@@ -63,10 +63,35 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -63,10 +63,35 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
//proxyServer.CertificateEngine = CertificateEngine.DefaultWindows; //proxyServer.CertificateEngine = CertificateEngine.DefaultWindows;
////Set a password for the .pfx file
//proxyServer.PfxPassword = "PfxPassword";
////Set Name(path) of the Root certificate file
//proxyServer.PfxFilePath = @"C:\NameFolder\rootCert.pfx";
////do you want Replace an existing Root certificate file(.pfx) if password is incorrect(RootCertificate=null)? yes====>true
//proxyServer.OverwritePfxFile = true;
////save all fake certificates in folder "crts"(will be created in proxy dll directory)
////if create new Root certificate file(.pfx) ====> delete folder "crts"
//proxyServer.SaveFakeCertificates = true;
//Trust Root Certificate
proxyServer.TrustRootCertificate = true; proxyServer.TrustRootCertificate = true;
proxyServer.CertificateManager.TrustRootCertificateAsAdministrator(); 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
//proxyServer.EnsureRootCertificate(true);
////or load directly certificate(As Administrator if need this)
////and At the same time chose path and password
////if password is incorrect and (overwriteRootCert=true)(RootCertificate=null) ====> replace an existing .pfx file
////note : load now (if existed)
//proxyServer.CertificateManager.LoadRootCertificate(@"C:\NameFolder\rootCert.pfx", "PfxPassword");
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{ {
ExcludedHttpsHostNameRegex = new[] { "ssllabs.com" }, ExcludedHttpsHostNameRegex = new[] { "ssllabs.com" },
......
...@@ -29,11 +29,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -29,11 +29,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
private readonly int bufferSize; private readonly int bufferSize;
/// <summary>
/// Holds a reference to proxy response handler method
/// </summary>
private Func<SessionEventArgs, Task> httpResponseHandler;
private readonly Action<Exception> exceptionFunc; private readonly Action<Exception> exceptionFunc;
/// <summary> /// <summary>
...@@ -108,11 +103,9 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -108,11 +103,9 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
internal SessionEventArgs(int bufferSize, internal SessionEventArgs(int bufferSize,
ProxyEndPoint endPoint, ProxyEndPoint endPoint,
Func<SessionEventArgs, Task> httpResponseHandler,
Action<Exception> exceptionFunc) Action<Exception> exceptionFunc)
{ {
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
this.httpResponseHandler = httpResponseHandler;
this.exceptionFunc = exceptionFunc; this.exceptionFunc = exceptionFunc;
ProxyClient = new ProxyClient(); ProxyClient = new ProxyClient();
...@@ -536,16 +529,18 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -536,16 +529,18 @@ 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) public async Task Ok(string html, Dictionary<string, HttpHeader> headers = null)
{ {
var response = new OkResponse(); var response = new OkResponse();
if (headers != null)
{
response.Headers.AddHeaders(headers); response.Headers.AddHeaders(headers);
}
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); await Respond(response);
WebSession.Request.CancelRequest = true;
} }
/// <summary> /// <summary>
...@@ -635,8 +630,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -635,8 +630,6 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Response = response; WebSession.Response = response;
await httpResponseHandler(this);
WebSession.Request.CancelRequest = true; WebSession.Request.CancelRequest = true;
} }
...@@ -645,7 +638,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -645,7 +638,6 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
httpResponseHandler = null;
CustomUpStreamProxyUsed = null; CustomUpStreamProxyUsed = null;
DataSent = null; DataSent = null;
......
...@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -9,7 +9,7 @@ namespace Titanium.Web.Proxy.EventArguments
public bool IsHttpsConnect { get; set; } public bool IsHttpsConnect { get; set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, Action<Exception> exceptionFunc) internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, Action<Exception> exceptionFunc)
: base(bufferSize, endPoint, null, exceptionFunc) : base(bufferSize, endPoint, exceptionFunc)
{ {
WebSession.Request = connectRequest; WebSession.Request = connectRequest;
} }
......
using System; using System;
using System.IO; using System.IO;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using Org.BouncyCastle.Asn1; using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs; using Org.BouncyCastle.Asn1.Pkcs;
...@@ -24,6 +25,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -24,6 +25,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// </summary> /// </summary>
internal class BCCertificateMaker : ICertificateMaker internal class BCCertificateMaker : ICertificateMaker
{ {
public static readonly Regex CNRemoverRegex = new Regex(@"^CN\s*=\s*", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private const int certificateValidDays = 1825; private const int certificateValidDays = 1825;
private const int certificateGraceDays = 366; private const int certificateGraceDays = 366;
...@@ -146,7 +149,7 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -146,7 +149,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
{ {
try try
{ {
x509Certificate.FriendlyName = subjectName; x509Certificate.FriendlyName = CNRemoverRegex.Replace(subjectName, string.Empty);
} }
catch (PlatformNotSupportedException) catch (PlatformNotSupportedException)
{ {
......
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
...@@ -69,10 +69,24 @@ namespace Titanium.Web.Proxy.Network ...@@ -69,10 +69,24 @@ namespace Titanium.Web.Proxy.Network
private string rootCertificateName; private string rootCertificateName;
private bool pfxFileExists = false;
private bool clearCertificates { get; set; } private bool clearCertificates { get; set; }
private X509Certificate2 rootCertificate; private X509Certificate2 rootCertificate;
internal bool trustRootCertificate { get; set; } = false;
internal bool OverwritePfXFile { get; set; } = true;
internal string PfxPassword { get; set; } = string.Empty;
internal string PfxFilePath { get; set; } = string.Empty;
internal X509KeyStorageFlags StorageFlag { get; set; } = X509KeyStorageFlags.Exportable;
internal bool SaveFakeCertificates { get; set; } = false;
/// <summary> /// <summary>
/// Cache dictionary /// Cache dictionary
/// </summary> /// </summary>
...@@ -86,7 +100,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -86,7 +100,7 @@ namespace Titanium.Web.Proxy.Network
set set
{ {
issuer = value; issuer = value;
ClearRootCertificate(); //ClearRootCertificate();
} }
} }
...@@ -96,7 +110,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -96,7 +110,7 @@ namespace Titanium.Web.Proxy.Network
set set
{ {
rootCertificateName = value; rootCertificateName = value;
ClearRootCertificate(); //ClearRootCertificate();
} }
} }
...@@ -115,7 +129,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -115,7 +129,6 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
internal bool CertValidated => RootCertificate != null; internal bool CertValidated => RootCertificate != null;
internal CertificateManager(Action<Exception> exceptionFunc) internal CertificateManager(Action<Exception> exceptionFunc)
{ {
this.exceptionFunc = exceptionFunc; this.exceptionFunc = exceptionFunc;
...@@ -124,13 +137,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -124,13 +137,13 @@ namespace Titanium.Web.Proxy.Network
certificateCache = new ConcurrentDictionary<string, CachedCertificate>(); certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
} }
private void ClearRootCertificate() public void ClearRootCertificate()
{ {
certificateCache.Clear(); certificateCache.Clear();
rootCertificate = null; rootCertificate = null;
} }
private string GetRootCertificatePath() private string GetRootCertificateDirectory()
{ {
string assemblyLocation = Assembly.GetExecutingAssembly().Location; string assemblyLocation = Assembly.GetExecutingAssembly().Location;
...@@ -143,18 +156,49 @@ namespace Titanium.Web.Proxy.Network ...@@ -143,18 +156,49 @@ 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();
string fileName = Path.Combine(path, "rootCert.pfx");
return path;
}
private string GetCertPath()
{
string path = GetRootCertificateDirectory();
string certPath = Path.Combine(path, "crts");
if (!Directory.Exists(certPath))
{
Directory.CreateDirectory(certPath);
}
return certPath;
}
private string GetRootCertificatePath()
{
string path = GetRootCertificateDirectory();
string fileName = PfxFilePath;
if (fileName == string.Empty)
{
fileName = Path.Combine(path, "rootCert.pfx");
StorageFlag = X509KeyStorageFlags.Exportable;
}
return fileName; return fileName;
} }
private X509Certificate2 LoadRootCertificate() public X509Certificate2 LoadRootCertificate()
{ {
string fileName = GetRootCertificatePath(); string fileName = GetRootCertificatePath();
if (!File.Exists(fileName)) pfxFileExists = File.Exists(fileName);
if (!pfxFileExists)
{
return null; return null;
}
try try
{ {
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable); return new X509Certificate2(fileName, PfxPassword, StorageFlag);
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -182,6 +226,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -182,6 +226,11 @@ namespace Titanium.Web.Proxy.Network
return true; return true;
} }
if (!OverwritePfXFile && pfxFileExists)
{
return false;
}
try try
{ {
RootCertificate = CreateCertificate(RootCertificateName, true); RootCertificate = CreateCertificate(RootCertificateName, true);
...@@ -195,8 +244,17 @@ namespace Titanium.Web.Proxy.Network ...@@ -195,8 +244,17 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
try
{
Directory.Delete(GetCertPath(), true);
}
catch
{
// ignore
}
string fileName = GetRootCertificatePath(); string fileName = GetRootCertificatePath();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12)); File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword));
} }
catch (Exception e) catch (Exception e)
{ {
...@@ -207,6 +265,28 @@ namespace Titanium.Web.Proxy.Network ...@@ -207,6 +265,28 @@ namespace Titanium.Web.Proxy.Network
return RootCertificate != null; return RootCertificate != null;
} }
/// <summary>
/// Manually load a Root certificate file(.pfx file)
/// </summary>
/// <param name="pfxFilePath">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)</param>
/// <param name="password">Set a password for the .pfx file</param>
/// <param name="overwritePfXFile">true : replace an existing .pfx file if password is incorect or if RootCertificate==null</param>
/// <param name="storageFlag"></param>
/// <returns>
/// true if succeeded, else false
/// </returns>
public bool LoadRootCertificate(string pfxFilePath, string password, bool overwritePfXFile = true, X509KeyStorageFlags storageFlag = X509KeyStorageFlags.Exportable)
{
PfxFilePath = pfxFilePath;
PfxPassword = password;
OverwritePfXFile = overwritePfXFile;
StorageFlag = storageFlag;
RootCertificate = LoadRootCertificate();
return (RootCertificate != null);
}
/// <summary> /// <summary>
/// Trusts the root certificate. /// Trusts the root certificate.
/// </summary> /// </summary>
...@@ -232,12 +312,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -232,12 +312,12 @@ namespace Titanium.Web.Proxy.Network
} }
string fileName = Path.GetTempFileName(); string fileName = Path.GetTempFileName();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12)); File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12, PfxPassword));
var info = new ProcessStartInfo var info = new ProcessStartInfo
{ {
FileName = "certutil.exe", FileName = "certutil.exe",
Arguments = "-importPFX -p \"\" -f \"" + fileName + "\"", Arguments = "-importPFX -p \""+ PfxPassword + "\" -f \"" + fileName + "\"",
CreateNoWindow = true, CreateNoWindow = true,
UseShellExecute = true, UseShellExecute = true,
Verb = "runas", Verb = "runas",
...@@ -352,6 +432,16 @@ namespace Titanium.Web.Proxy.Network ...@@ -352,6 +432,16 @@ namespace Titanium.Web.Proxy.Network
} }
} }
private X509Certificate2 MakeCertificate(string certificateName, bool isRootCertificate)
{
if (!isRootCertificate && RootCertificate == null)
{
CreateTrustedRootCertificate();
}
return certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate);
}
/// <summary> /// <summary>
/// Create an SSL certificate /// Create an SSL certificate
/// </summary> /// </summary>
...@@ -374,17 +464,40 @@ namespace Titanium.Web.Proxy.Network ...@@ -374,17 +464,40 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
if (!isRootCertificate && RootCertificate == null) if (!isRootCertificate && SaveFakeCertificates)
{ {
CreateTrustedRootCertificate(); string path = GetCertPath();
} string subjectName = BCCertificateMaker.CNRemoverRegex.Replace(certificateName, string.Empty);
subjectName = subjectName.Replace("*", "$x$");
subjectName = Path.Combine(path, subjectName + ".pfx");
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate); 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) catch (Exception e)
{ {
exceptionFunc(e); exceptionFunc(e);
} }
if (certificate != null && !certificateCache.ContainsKey(certificateName)) if (certificate != null && !certificateCache.ContainsKey(certificateName))
{ {
certificateCache.Add(certificateName, new CachedCertificate certificateCache.Add(certificateName, new CachedCertificate
......
...@@ -88,10 +88,10 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -88,10 +88,10 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
string httpStatus = await reader.ReadLineAsync(); string httpStatus = await reader.ReadLineAsync();
Response.ParseResponseLine(httpStatus, out var version, out int statusCode, out string statusDescription); Response.ParseResponseLine(httpStatus, out _, out int statusCode, out string statusDescription);
if (!statusDescription.EqualsIgnoreCase("200 OK") if (statusCode != 200 && !statusDescription.EqualsIgnoreCase("OK")
&& !statusDescription.EqualsIgnoreCase("connection established")) && !statusDescription.EqualsIgnoreCase("Connection Established"))
{ {
throw new Exception("Upstream proxy failed to create a secure tunnel"); throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
......
...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -17,7 +17,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="tcpRows">TcpRow collection to initialize with.</param> /// <param name="tcpRows">TcpRow collection to initialize with.</param>
internal TcpTable(IEnumerable<TcpRow> tcpRows) internal TcpTable(IEnumerable<TcpRow> tcpRows)
{ {
this.TcpRows = tcpRows; TcpRows = tcpRows;
} }
/// <summary> /// <summary>
......
using StreamExtended.Network; using StreamExtended.Network;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
...@@ -37,11 +37,6 @@ namespace Titanium.Web.Proxy ...@@ -37,11 +37,6 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private Action<Exception> exceptionFunc; private Action<Exception> exceptionFunc;
/// <summary>
/// Backing field for corresponding public property
/// </summary>
private bool trustRootCertificate;
/// <summary> /// <summary>
/// Backing field for corresponding public property /// Backing field for corresponding public property
/// </summary> /// </summary>
...@@ -52,6 +47,8 @@ namespace Titanium.Web.Proxy ...@@ -52,6 +47,8 @@ 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>
...@@ -118,14 +115,64 @@ namespace Titanium.Web.Proxy ...@@ -118,14 +115,64 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public bool TrustRootCertificate public bool TrustRootCertificate
{ {
get => trustRootCertificate; get => CertificateManager.trustRootCertificate;
set 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>
/// Password of the Root certificate file
/// <para>Set a password for the .pfx file</para>
/// </summary>
public string PfxPassword
{ {
trustRootCertificate = value; get => CertificateManager.PfxPassword;
if (value) 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
{ {
EnsureRootCertificate(); get => CertificateManager.PfxFilePath;
set => CertificateManager.PfxFilePath = value;
} }
public X509KeyStorageFlags StorageFlag
{
get => storageFlag;
set
{
storageFlag = value;
CertificateManager.StorageFlag = storageFlag;
} }
} }
...@@ -373,7 +420,6 @@ namespace Titanium.Web.Proxy ...@@ -373,7 +420,6 @@ namespace Titanium.Web.Proxy
SetAsSystemProxy(endPoint, ProxyProtocolType.Http); SetAsSystemProxy(endPoint, ProxyProtocolType.Http);
} }
/// <summary> /// <summary>
/// Set the given explicit end point as the default proxy server for current machine /// Set the given explicit end point as the default proxy server for current machine
/// </summary> /// </summary>
...@@ -520,6 +566,11 @@ namespace Titanium.Web.Proxy ...@@ -520,6 +566,11 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running."); throw new Exception("Proxy is already running.");
} }
if (ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.GenericCertificate == null))
{
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)
//due to non gracious proxy shutdown before or something else //due to non gracious proxy shutdown before or something else
if (systemProxySettingsManager != null && RunTime.IsWindows) if (systemProxySettingsManager != null && RunTime.IsWindows)
...@@ -571,7 +622,6 @@ namespace Titanium.Web.Proxy ...@@ -571,7 +622,6 @@ namespace Titanium.Web.Proxy
} }
} }
/// <summary> /// <summary>
/// Stop this proxy server /// Stop this proxy server
/// </summary> /// </summary>
...@@ -604,26 +654,6 @@ namespace Titanium.Web.Proxy ...@@ -604,26 +654,6 @@ namespace Titanium.Web.Proxy
ProxyRunning = false; ProxyRunning = false;
} }
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="serverConnection"></param>
private void Dispose(CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, TcpConnection serverConnection)
{
clientStreamReader?.Dispose();
clientStream?.Dispose();
if (serverConnection != null)
{
serverConnection.Dispose();
serverConnection = null;
}
}
/// <summary> /// <summary>
/// Dispose Proxy. /// Dispose Proxy.
/// </summary> /// </summary>
...@@ -692,7 +722,17 @@ namespace Titanium.Web.Proxy ...@@ -692,7 +722,17 @@ namespace Titanium.Web.Proxy
return Task.FromResult(proxy); return Task.FromResult(proxy);
} }
private void EnsureRootCertificate() /// <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) if (!CertificateManager.CertValidated)
{ {
...@@ -702,6 +742,11 @@ namespace Titanium.Web.Proxy ...@@ -702,6 +742,11 @@ namespace Titanium.Web.Proxy
{ {
CertificateManager.TrustRootCertificate(); CertificateManager.TrustRootCertificate();
} }
if (TrustRootCertificateAsAdministrator)
{
CertificateManager.TrustRootCertificateAsAdministrator();
}
} }
} }
...@@ -753,13 +798,13 @@ namespace Titanium.Web.Proxy ...@@ -753,13 +798,13 @@ namespace Titanium.Web.Proxy
try try
{ {
if (endPoint.GetType() == typeof(TransparentProxyEndPoint)) if (endPoint is TransparentProxyEndPoint tep)
{ {
await HandleClient(endPoint as TransparentProxyEndPoint, tcpClient); await HandleClient(tep, tcpClient);
} }
else else
{ {
await HandleClient(endPoint as ExplicitProxyEndPoint, tcpClient); await HandleClient((ExplicitProxyEndPoint)endPoint, tcpClient);
} }
} }
finally finally
......
...@@ -38,16 +38,19 @@ namespace Titanium.Web.Proxy ...@@ -38,16 +38,19 @@ namespace Titanium.Web.Proxy
/// <returns></returns> /// <returns></returns>
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{ {
bool disposed = false;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize); var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize); var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
Uri httpRemoteUri;
try try
{
string connectHostname = null;
ConnectRequest connectRequest = null;
//Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (await IsConnectMethod(clientStream) == 1)
{ {
//read the first line HTTP command //read the first line HTTP command
string httpCmd = await clientStreamReader.ReadLineAsync(); string httpCmd = await clientStreamReader.ReadLineAsync();
...@@ -56,28 +59,24 @@ namespace Titanium.Web.Proxy ...@@ -56,28 +59,24 @@ namespace Titanium.Web.Proxy
return; return;
} }
Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); Request.ParseRequestLine(httpCmd, out string _, out string httpUrl, out var version);
httpRemoteUri = httpMethod == "CONNECT" ? new Uri("http://" + httpUrl) : new Uri(httpUrl); var httpRemoteUri = new Uri("http://" + httpUrl);
connectHostname = httpRemoteUri.Host;
//filter out excluded host names //filter out excluded host names
bool excluded = false; bool excluded = false;
if (endPoint.ExcludedHttpsHostNameRegex != null) if (endPoint.ExcludedHttpsHostNameRegex != null)
{ {
excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host)); excluded = endPoint.ExcludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname));
} }
if (endPoint.IncludedHttpsHostNameRegex != null) if (endPoint.IncludedHttpsHostNameRegex != null)
{ {
excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(httpRemoteUri.Host)); excluded = !endPoint.IncludedHttpsHostNameRegexList.Any(x => x.IsMatch(connectHostname));
} }
ConnectRequest connectRequest = null;
//Client wants to create a secure tcp tunnel (probably its a HTTPS or Websocket request)
if (httpMethod == "CONNECT")
{
connectRequest = new ConnectRequest connectRequest = new ConnectRequest
{ {
RequestUri = httpRemoteUri, RequestUri = httpRemoteUri,
...@@ -125,8 +124,7 @@ namespace Titanium.Web.Proxy ...@@ -125,8 +124,7 @@ namespace Titanium.Web.Proxy
if (!excluded && isClientHello) if (!excluded && isClientHello)
{ {
httpRemoteUri = new Uri("https://" + httpUrl); connectRequest.RequestUri = new Uri("https://" + httpUrl);
connectRequest.RequestUri = httpRemoteUri;
SslStream sslStream = null; SslStream sslStream = null;
...@@ -134,7 +132,7 @@ namespace Titanium.Web.Proxy ...@@ -134,7 +132,7 @@ namespace Titanium.Web.Proxy
{ {
sslStream = new SslStream(clientStream); sslStream = new SslStream(clientStream);
string certName = HttpHelper.GetWildCardDomainName(httpRemoteUri.Host); string certName = HttpHelper.GetWildCardDomainName(connectHostname);
var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false); var certificate = endPoint.GenericCertificate ?? CertificateManager.CreateCertificate(certName, false);
...@@ -154,12 +152,7 @@ namespace Titanium.Web.Proxy ...@@ -154,12 +152,7 @@ namespace Titanium.Web.Proxy
return; return;
} }
if (await CanBeHttpMethod(clientStream)) if (await IsConnectMethod(clientStream) == -1)
{
//Now read the actual HTTPS request line
httpCmd = await clientStreamReader.ReadLineAsync();
}
else
{ {
// It can be for example some Google (Cloude Messaging for Chrome) magic // It can be for example some Google (Cloude Messaging for Chrome) magic
excluded = true; excluded = true;
...@@ -207,8 +200,11 @@ namespace Titanium.Web.Proxy ...@@ -207,8 +200,11 @@ namespace Titanium.Web.Proxy
} }
//Now create the request //Now create the request
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter, connectHostname, endPoint, connectRequest);
httpRemoteUri.Scheme == UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequest); }
catch (ProxyHttpException e)
{
ExceptionFunc(e);
} }
catch (IOException e) catch (IOException e)
{ {
...@@ -220,44 +216,53 @@ namespace Titanium.Web.Proxy ...@@ -220,44 +216,53 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
// is this the correct error message? ExceptionFunc(new Exception("Error occured in whilst handling the client", e));
ExceptionFunc(new Exception("Error whilst authorizing request", e));
} }
finally finally
{ {
if (!disposed) clientStreamReader.Dispose();
{ clientStream.Dispose();
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
} }
} }
private async Task<bool> CanBeHttpMethod(CustomBufferedStream clientStream) /// <summary>
/// Determines whether is connect method.
/// </summary>
/// <param name="clientStream">The client stream.</param>
/// <returns>1: when CONNECT, 0: when valid HTTP method, -1: otherwise</returns>
private async Task<int> IsConnectMethod(CustomBufferedStream clientStream)
{ {
bool isConnect = true;
int legthToCheck = 10; int legthToCheck = 10;
for (int i = 0; i < legthToCheck; i++) for (int i = 0; i < legthToCheck; i++)
{ {
int b = await clientStream.PeekByteAsync(i); int b = await clientStream.PeekByteAsync(i);
if (b == -1) if (b == -1)
{ {
return false; return -1;
} }
if (b == ' ' && i > 2) if (b == ' ' && i > 2)
{ {
// at least 3 letters and a space // at least 3 letters and a space
return true; return isConnect ? 1 : 0;
} }
if (!char.IsLetter((char)b)) char ch = (char)b;
if (!char.IsLetter(ch))
{ {
// non letter or too short // non letter or too short
return false; return -1;
}
if (i > 6 || ch != "CONNECT"[i])
{
isConnect = false;
} }
} }
// only letters // only letters
return true; return isConnect ? 1 : 0;
} }
/// <summary> /// <summary>
...@@ -269,11 +274,10 @@ namespace Titanium.Web.Proxy ...@@ -269,11 +274,10 @@ namespace Titanium.Web.Proxy
/// <returns></returns> /// <returns></returns>
private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient) private async Task HandleClient(TransparentProxyEndPoint endPoint, TcpClient tcpClient)
{ {
bool disposed = false;
var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize); var clientStream = new CustomBufferedStream(tcpClient.GetStream(), BufferSize);
CustomBinaryReader clientStreamReader = null; var clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
HttpResponseWriter clientStreamWriter = null; var clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
try try
{ {
...@@ -298,22 +302,14 @@ namespace Titanium.Web.Proxy ...@@ -298,22 +302,14 @@ namespace Titanium.Web.Proxy
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
} }
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
//now read the request line
string httpCmd = await clientStreamReader.ReadLineAsync();
//Now create the request //Now create the request
disposed = await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter, await HandleHttpSessionRequest(tcpClient, clientStream, clientStreamReader, clientStreamWriter,
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null, true); endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null, true);
} }
finally finally
{ {
if (!disposed) clientStreamReader.Dispose();
{ clientStream.Dispose();
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
}
} }
} }
...@@ -323,7 +319,6 @@ namespace Titanium.Web.Proxy ...@@ -323,7 +319,6 @@ namespace Titanium.Web.Proxy
/// client/server abruptly terminates connection or by normal HTTP termination /// client/server abruptly terminates connection or by normal HTTP termination
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="httpCmd"></param>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param> /// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param> /// <param name="clientStreamWriter"></param>
...@@ -332,24 +327,26 @@ namespace Titanium.Web.Proxy ...@@ -332,24 +327,26 @@ namespace Titanium.Web.Proxy
/// <param name="connectRequest"></param> /// <param name="connectRequest"></param>
/// <param name="isTransparentEndPoint"></param> /// <param name="isTransparentEndPoint"></param>
/// <returns></returns> /// <returns></returns>
private async Task<bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, CustomBufferedStream clientStream, private async Task HandleHttpSessionRequest(TcpClient client, CustomBufferedStream clientStream,
CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname,
ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false) ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false)
{ {
bool disposed = false;
TcpConnection connection = null; TcpConnection connection = null;
try
{
//Loop through each subsequest request on this particular client connection //Loop through each subsequest request on this particular client connection
//(assuming HTTP connection is kept alive by client) //(assuming HTTP connection is kept alive by client)
while (true) while (true)
{ {
// read the request line
string httpCmd = await clientStreamReader.ReadLineAsync();
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
{ {
break; break;
} }
var args = new SessionEventArgs(BufferSize, endPoint, HandleHttpSessionResponse, ExceptionFunc) var args = new SessionEventArgs(BufferSize, endPoint, ExceptionFunc)
{ {
ProxyClient = { TcpClient = client }, ProxyClient = { TcpClient = client },
WebSession = { ConnectRequest = connectRequest } WebSession = { ConnectRequest = connectRequest }
...@@ -406,7 +403,6 @@ namespace Titanium.Web.Proxy ...@@ -406,7 +403,6 @@ namespace Titanium.Web.Proxy
//proxy authorization check //proxy authorization check
if (httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false) if (httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false)
{ {
args.Dispose();
break; break;
} }
...@@ -432,7 +428,7 @@ namespace Titanium.Web.Proxy ...@@ -432,7 +428,7 @@ namespace Titanium.Web.Proxy
if (args.WebSession.Request.CancelRequest) if (args.WebSession.Request.CancelRequest)
{ {
args.Dispose(); await HandleHttpSessionResponse(args);
break; break;
} }
...@@ -480,45 +476,32 @@ namespace Titanium.Web.Proxy ...@@ -480,45 +476,32 @@ namespace Titanium.Web.Proxy
(buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); },
ExceptionFunc); ExceptionFunc);
args.Dispose();
break; break;
} }
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
disposed = await HandleHttpSessionRequestInternal(connection, args, false); await HandleHttpSessionRequestInternal(connection, args);
if (disposed)
{
//already disposed inside above method
args.Dispose();
break;
}
//if connection is closing exit //if connection is closing exit
if (args.WebSession.Response.KeepAlive == false) if (args.WebSession.Response.KeepAlive == false)
{ {
args.Dispose();
break; break;
} }
args.Dispose();
// read the next request
httpCmd = await clientStreamReader.ReadLineAsync();
} }
catch (Exception e) catch (Exception e) when (!(e is ProxyHttpException))
{ {
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args)); throw new ProxyHttpException("Error occured whilst handling session request", e, args);
break; }
finally
{
args.Dispose();
} }
} }
}
if (!disposed) finally
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, connection); connection?.Dispose();
} }
return true;
} }
/// <summary> /// <summary>
...@@ -526,13 +509,9 @@ namespace Titanium.Web.Proxy ...@@ -526,13 +509,9 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
/// <param name="connection"></param> /// <param name="connection"></param>
/// <param name="args"></param> /// <param name="args"></param>
/// <param name="closeConnection"></param> /// <returns>True if close the connection</returns>
/// <returns></returns> private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args)
private async Task<bool> HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, bool closeConnection)
{ {
bool disposed = false;
bool keepAlive = false;
try try
{ {
var request = args.WebSession.Request; var request = args.WebSession.Request;
...@@ -605,45 +584,15 @@ namespace Titanium.Web.Proxy ...@@ -605,45 +584,15 @@ namespace Titanium.Web.Proxy
//If not expectation failed response was returned by server then parse response //If not expectation failed response was returned by server then parse response
if (!request.ExpectationFailed) if (!request.ExpectationFailed)
{ {
disposed = await HandleHttpSessionResponse(args); await HandleHttpSessionResponse(args);
//already disposed inside above method
if (disposed)
{
return true;
}
}
//if connection is closing exit
if (args.WebSession.Response.KeepAlive == false)
{
return true;
} }
if (!closeConnection)
{
keepAlive = true;
return false;
}
}
catch (Exception e)
{
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request (internal)", e, args));
return true;
} }
finally catch (Exception e) when (!(e is ProxyHttpException))
{
if (!disposed && !keepAlive)
{ {
//dispose throw new ProxyHttpException("Error occured whilst handling session request (internal)", e, args);
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
} }
} }
return true;
}
/// <summary> /// <summary>
/// Create a Server Connection /// Create a Server Connection
/// </summary> /// </summary>
......
...@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy ...@@ -18,7 +18,7 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <returns>true if client/server connection was terminated (and disposed) </returns> /// <returns>true if client/server connection was terminated (and disposed) </returns>
private async Task<bool> HandleHttpSessionResponse(SessionEventArgs args) private async Task HandleHttpSessionResponse(SessionEventArgs args)
{ {
try try
{ {
...@@ -30,12 +30,7 @@ namespace Titanium.Web.Proxy ...@@ -30,12 +30,7 @@ namespace Titanium.Web.Proxy
//check for windows authentication //check for windows authentication
if (isWindowsAuthenticationEnabledAndSupported && response.StatusCode == (int)HttpStatusCode.Unauthorized) if (isWindowsAuthenticationEnabledAndSupported && response.StatusCode == (int)HttpStatusCode.Unauthorized)
{ {
bool disposed = await Handle401UnAuthorized(args); await Handle401UnAuthorized(args);
if (disposed)
{
return true;
}
} }
args.ReRequest = false; args.ReRequest = false;
...@@ -52,8 +47,8 @@ namespace Titanium.Web.Proxy ...@@ -52,8 +47,8 @@ namespace Titanium.Web.Proxy
{ {
//clear current response //clear current response
await args.ClearResponse(); await args.ClearResponse();
bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args);
return disposed; return;
} }
response.ResponseLocked = true; response.ResponseLocked = true;
...@@ -109,16 +104,10 @@ namespace Titanium.Web.Proxy ...@@ -109,16 +104,10 @@ namespace Titanium.Web.Proxy
} }
} }
} }
catch (Exception e) catch (Exception e) when (!(e is ProxyHttpException))
{ {
ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args)); throw new ProxyHttpException("Error occured whilst handling session response", e, args);
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter,
args.WebSession.ServerConnection);
return true;
} }
return false;
} }
/// <summary> /// <summary>
......
...@@ -135,8 +135,7 @@ namespace Titanium.Web.Proxy ...@@ -135,8 +135,7 @@ namespace Titanium.Web.Proxy
//request again with updated authorization header //request again with updated authorization header
//and server cookies //and server cookies
bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args);
return disposed;
} }
return false; return false;
......
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