Unverified Commit b27ba8cf authored by honfika's avatar honfika Committed by GitHub

Merge pull request #387 from justcoding121/develop

Merge to beta
parents 553c89ba 2b9a7d35
......@@ -69,11 +69,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
};
//Fired when a CONNECT request is received
explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect;
explicitEndPoint.TunnelConnectRequest += OnTunnelConnectRequest;
explicitEndPoint.TunnelConnectResponse += OnTunnelConnectResponse;
explicitEndPoint.BeforeTunnelConnectRequest += OnBeforeTunnelConnectRequest;
explicitEndPoint.BeforeTunnelConnectResponse += OnBeforeTunnelConnectResponse;
//An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner
......@@ -111,9 +108,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
public void Stop()
{
explicitEndPoint.BeforeTunnelConnect -= OnBeforeTunnelConnect;
explicitEndPoint.TunnelConnectRequest -= OnTunnelConnectRequest;
explicitEndPoint.TunnelConnectResponse -= OnTunnelConnectResponse;
explicitEndPoint.BeforeTunnelConnectRequest -= OnBeforeTunnelConnectRequest;
explicitEndPoint.BeforeTunnelConnectResponse -= OnBeforeTunnelConnectResponse;
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
......@@ -126,27 +122,21 @@ namespace Titanium.Web.Proxy.Examples.Basic
//proxyServer.CertificateManager.RemoveTrustedRootCertificates();
}
private async Task<bool> OnBeforeTunnelConnect(string hostname)
private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.WebSession.Request.RequestUri.Host;
Console.WriteLine("Tunnel to: " + 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);
e.Excluded = true;
}
}
private async Task OnTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
Console.WriteLine("Tunnel to: " + e.WebSession.Request.Host);
}
private async Task OnTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e)
private async Task OnBeforeTunnelConnectResponse(object sender, TunnelConnectSessionEventArgs e)
{
}
......
......@@ -102,8 +102,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf
proxyServer.BeforeRequest += ProxyServer_BeforeRequest;
proxyServer.BeforeResponse += ProxyServer_BeforeResponse;
explicitEndPoint.TunnelConnectRequest += ProxyServer_TunnelConnectRequest;
explicitEndPoint.TunnelConnectResponse += ProxyServer_TunnelConnectResponse;
explicitEndPoint.BeforeTunnelConnectRequest += ProxyServer_BeforeTunnelConnectRequest;
explicitEndPoint.BeforeTunnelConnectResponse += ProxyServer_BeforeTunnelConnectResponse;
proxyServer.ClientConnectionCountChanged += delegate { Dispatcher.Invoke(() => { ClientConnectionCount = proxyServer.ClientConnectionCount; }); };
proxyServer.ServerConnectionCountChanged += delegate { Dispatcher.Invoke(() => { ServerConnectionCount = proxyServer.ServerConnectionCount; }); };
proxyServer.Start();
......@@ -113,15 +113,21 @@ namespace Titanium.Web.Proxy.Examples.Wpf
InitializeComponent();
}
private async Task ProxyServer_TunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
private async Task ProxyServer_BeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.WebSession.Request.RequestUri.Host;
if (hostname.EndsWith("webex.com"))
{
e.Excluded = true;
}
await Dispatcher.InvokeAsync(() =>
{
AddSession(e);
});
}
private async Task ProxyServer_TunnelConnectResponse(object sender, SessionEventArgs e)
private async Task ProxyServer_BeforeTunnelConnectResponse(object sender, SessionEventArgs e)
{
await Dispatcher.InvokeAsync(() =>
{
......
......@@ -51,8 +51,8 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="StreamExtended, Version=1.0.135.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.135-beta\lib\net45\StreamExtended.dll</HintPath>
<Reference Include="StreamExtended, Version=1.0.138.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\packages\StreamExtended.1.0.138-beta\lib\net45\StreamExtended.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="StreamExtended" version="1.0.135-beta" targetFramework="net45" />
<package id="StreamExtended" version="1.0.138-beta" targetFramework="net45" />
</packages>
\ No newline at end of file
......@@ -126,18 +126,16 @@ Sample request and response event handlers
private IDictionary<Guid, string> requestBodyHistory
= new ConcurrentDictionary<Guid, string>();
private async Task<bool> OnBeforeTunnelConnect(string hostname)
private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{
string hostname = e.WebSession.Request.RequestUri.Host;
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);
e.Excluded = true;
}
}
......
......@@ -19,7 +19,7 @@ namespace Titanium.Web.Proxy.UnitTests
{
var tasks = new List<Task>();
var mgr = new CertificateManager(null, null, false, false, false, new Lazy<Action<Exception>>(() => (e =>
var mgr = new CertificateManager(null, null, false, false, false, new Lazy<ExceptionHandler>(() => (e =>
{
//Console.WriteLine(e.ToString() + e.InnerException != null ? e.InnerException.ToString() : string.Empty);
})).Value);
......@@ -48,7 +48,7 @@ namespace Titanium.Web.Proxy.UnitTests
{
var tasks = new List<Task>();
var mgr = new CertificateManager(null, null, false, false, false, new Lazy<Action<Exception>>(() => (e =>
var mgr = new CertificateManager(null, null, false, false, false, new Lazy<ExceptionHandler>(() => (e =>
{
//Console.WriteLine(e.ToString() + e.InnerException != null ? e.InnerException.ToString() : string.Empty);
})).Value);
......
......@@ -29,7 +29,7 @@ namespace Titanium.Web.Proxy
};
//why is the sender null?
ServerCertificateValidationCallback.InvokeParallel(this, args);
ServerCertificateValidationCallback.InvokeAsync(this, args, exceptionFunc).Wait();
return args.IsValid;
}
......@@ -88,7 +88,7 @@ namespace Titanium.Web.Proxy
};
//why is the sender null?
ClientCertificateSelectionCallback.InvokeParallel(this, args);
ClientCertificateSelectionCallback.InvokeAsync(this, args, exceptionFunc).Wait();
return args.ClientCertificate;
}
......
......@@ -29,7 +29,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
private readonly int bufferSize;
private readonly Action<Exception> exceptionFunc;
private readonly ExceptionHandler exceptionFunc;
/// <summary>
/// Backing field for corresponding public property
......@@ -105,7 +105,7 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
internal SessionEventArgs(int bufferSize,
ProxyEndPoint endPoint,
Action<Exception> exceptionFunc)
ExceptionHandler exceptionFunc)
{
this.bufferSize = bufferSize;
this.exceptionFunc = exceptionFunc;
......
......@@ -6,9 +6,11 @@ namespace Titanium.Web.Proxy.EventArguments
{
public class TunnelConnectSessionEventArgs : SessionEventArgs
{
public bool IsHttpsConnect { get; set; }
public bool Excluded { get; set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, Action<Exception> exceptionFunc)
public bool IsHttpsConnect { get; internal set; }
internal TunnelConnectSessionEventArgs(int bufferSize, ProxyEndPoint endPoint, ConnectRequest connectRequest, ExceptionHandler exceptionFunc)
: base(bufferSize, endPoint, exceptionFunc)
{
WebSession.Request = connectRequest;
......
using System;
namespace Titanium.Web.Proxy
{
public delegate void ExceptionHandler(Exception exception);
}
\ No newline at end of file
......@@ -9,7 +9,7 @@
/// Constructor.
/// </summary>
/// <param name="message"></param>
public BodyNotFoundException(string message) : base(message)
internal BodyNotFoundException(string message) : base(message)
{
}
}
......
using System;
using System.Collections.Generic;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Exceptions
......@@ -13,13 +14,17 @@ namespace Titanium.Web.Proxy.Exceptions
/// Instantiate new instance
/// </summary>
/// <param name="message">Exception message</param>
/// <param name="session">The <see cref="SessionEventArgs"/> instance containing the event data.</param>
/// <param name="innerException">Inner exception associated to upstream proxy authorization</param>
/// <param name="headers">Http's headers associated</param>
public ProxyAuthorizationException(string message, Exception innerException, IEnumerable<HttpHeader> headers) : base(message, innerException)
internal ProxyAuthorizationException(string message, SessionEventArgs session, Exception innerException, IEnumerable<HttpHeader> headers) : base(message, innerException)
{
Session = session;
Headers = headers;
}
public SessionEventArgs Session { get; }
/// <summary>
/// Headers associated with the authorization exception
/// </summary>
......
......@@ -14,7 +14,7 @@ namespace Titanium.Web.Proxy.Exceptions
/// <param name="message">Message for this exception</param>
/// <param name="innerException">Associated inner exception</param>
/// <param name="sessionEventArgs">Instance of <see cref="EventArguments.SessionEventArgs"/> associated to the exception</param>
public ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(message, innerException)
internal ProxyHttpException(string message, Exception innerException, SessionEventArgs sessionEventArgs) : base(message, innerException)
{
SessionEventArgs = sessionEventArgs;
}
......
......@@ -6,33 +6,8 @@ namespace Titanium.Web.Proxy.Extensions
{
internal static class FuncExtensions
{
public static void InvokeParallel<T>(this AsyncEventHandler<T> callback, object sender, T args)
{
var invocationList = callback.GetInvocationList();
var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((AsyncEventHandler<T>)invocationList[i])(sender, args);
}
Task.WhenAll(handlerTasks).Wait();
}
public static async Task InvokeParallelAsync<T>(this AsyncEventHandler<T> callback, object sender, T args, Action<Exception> exceptionFunc)
{
var invocationList = callback.GetInvocationList();
var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = InternalInvokeAsync((AsyncEventHandler<T>)invocationList[i], sender, args, exceptionFunc);
}
await Task.WhenAll(handlerTasks);
}
public static async Task InvokeAsync<T>(this AsyncEventHandler<T> callback, object sender, T args, Action<Exception> exceptionFunc)
public static async Task InvokeAsync<T>(this AsyncEventHandler<T> callback, object sender, T args, ExceptionHandler exceptionFunc)
{
var invocationList = callback.GetInvocationList();
......@@ -42,16 +17,15 @@ namespace Titanium.Web.Proxy.Extensions
}
}
private static async Task InternalInvokeAsync<T>(AsyncEventHandler<T> callback, object sender, T args, Action<Exception> exceptionFunc)
private static async Task InternalInvokeAsync<T>(AsyncEventHandler<T> callback, object sender, T args, ExceptionHandler exceptionFunc)
{
try
{
await callback(sender, args);
}
catch (Exception ex)
catch (Exception e)
{
var ex2 = new Exception("Exception thrown in user event", ex);
exceptionFunc(ex2);
exceptionFunc(new Exception("Exception thrown in user event", e));
}
}
}
......
......@@ -122,7 +122,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal static async Task SendRawApm(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, Action<Exception> exceptionFunc)
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, ExceptionHandler exceptionFunc)
{
var tcs = new TaskCompletionSource<bool>();
var cts = new CancellationTokenSource();
......@@ -145,7 +145,7 @@ namespace Titanium.Web.Proxy.Helpers
}
private static void BeginRead(Stream inputStream, Stream outputStream, byte[] buffer, CancellationTokenSource cts, Action<byte[], int, int> onCopy,
Action<Exception> exceptionFunc)
ExceptionHandler exceptionFunc)
{
if (cts.IsCancellationRequested)
{
......@@ -221,7 +221,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal static async Task SendRawTap(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, Action<Exception> exceptionFunc)
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, ExceptionHandler exceptionFunc)
{
var cts = new CancellationTokenSource();
......@@ -247,7 +247,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal static Task SendRaw(Stream clientStream, Stream serverStream, int bufferSize,
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, Action<Exception> exceptionFunc)
Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive, ExceptionHandler exceptionFunc)
{
#if NET45
return SendRawApm(clientStream, serverStream, bufferSize, onDataSend, onDataReceive, exceptionFunc);
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public abstract class ProxyEndPoint
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
protected ProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
{
IpAddress = ipAddress;
Port = port;
EnableSsl = enableSsl;
}
/// <summary>
/// underlying TCP Listener object
/// </summary>
internal TcpListener Listener { get; set; }
/// <summary>
/// Ip Address we are listening.
/// </summary>
public IPAddress IpAddress { get; internal set; }
/// <summary>
/// Port we are listening.
/// </summary>
public int Port { get; internal set; }
/// <summary>
/// Enable SSL?
/// </summary>
public bool EnableSsl { get; internal set; }
/// <summary>
/// Is IPv6 enabled?
/// </summary>
public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any)
|| Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None);
}
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
{
internal List<Regex> ExcludedHttpsHostNameRegexList;
internal List<Regex> IncludedHttpsHostNameRegexList;
internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; }
/// <summary>
/// Generic certificate to use for SSL decryption.
/// </summary>
public X509Certificate2 GenericCertificate { get; set; }
/// <summary>
/// Return true if this HTTP connect request should'nt be decrypted and instead be relayed
/// Valid only for explicit endpoints
/// </summary>
public Func<string, Task<bool>> BeforeTunnelConnect;
/// <summary>
/// Intercept tunnel connect request
/// Valid only for explicit endpoints
/// </summary>
public event AsyncEventHandler<TunnelConnectSessionEventArgs> TunnelConnectRequest;
/// <summary>
/// Intercept tunnel connect response
/// Valid only for explicit endpoints
/// </summary>
public event AsyncEventHandler<TunnelConnectSessionEventArgs> TunnelConnectResponse;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) : base(ipAddress, port, enableSsl)
{
}
internal async Task InvokeTunnectConnectRequest(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, Action<Exception> exceptionFunc)
{
if (TunnelConnectRequest != null)
{
await TunnelConnectRequest.InvokeAsync(proxyServer, connectArgs, exceptionFunc);
}
}
internal async Task InvokeTunnectConnectResponse(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, Action<Exception> exceptionFunc)
{
if (TunnelConnectResponse != null)
{
await TunnelConnectResponse.InvokeAsync(proxyServer, connectArgs, exceptionFunc);
}
}
internal async Task InvokeTunnectConnectResponse(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, Action<Exception> exceptionFunc, bool isClientHello)
{
if (TunnelConnectResponse != null)
{
connectArgs.IsHttpsConnect = isClientHello;
await TunnelConnectResponse.InvokeAsync(proxyServer, connectArgs, exceptionFunc);
}
}
}
/// <summary>
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint
{
/// <summary>
/// Name of the Certificate need to be sent (same as the hostname we want to proxy)
/// This is valid only when UseServerNameIndication is set to false
/// </summary>
public string GenericCertificateName { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) : base(ipAddress, port, enableSsl)
{
GenericCertificateName = "localhost";
}
}
}
using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Extensions;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// A proxy endpoint that the client is aware of
/// So client application know that it is communicating with a proxy server
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
{
internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; }
/// <summary>
/// Generic certificate to use for SSL decryption.
/// </summary>
public X509Certificate2 GenericCertificate { get; set; }
/// <summary>
/// Intercept tunnel connect request
/// Valid only for explicit endpoints
/// Set the <see cref="TunnelConnectSessionEventArgs.Excluded"/> property to true if this HTTP connect request should'nt be decrypted and instead be relayed
/// </summary>
public event AsyncEventHandler<TunnelConnectSessionEventArgs> BeforeTunnelConnectRequest;
/// <summary>
/// Intercept tunnel connect response
/// Valid only for explicit endpoints
/// </summary>
public event AsyncEventHandler<TunnelConnectSessionEventArgs> BeforeTunnelConnectResponse;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) : base(ipAddress, port, enableSsl)
{
}
internal async Task InvokeBeforeTunnelConnectRequest(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc)
{
if (BeforeTunnelConnectRequest != null)
{
await BeforeTunnelConnectRequest.InvokeAsync(proxyServer, connectArgs, exceptionFunc);
}
}
internal async Task InvokeBeforeTunnectConnectResponse(ProxyServer proxyServer, TunnelConnectSessionEventArgs connectArgs, ExceptionHandler exceptionFunc, bool isClientHello = false)
{
if (BeforeTunnelConnectResponse != null)
{
connectArgs.IsHttpsConnect = isClientHello;
await BeforeTunnelConnectResponse.InvokeAsync(proxyServer, connectArgs, exceptionFunc);
}
}
}
}
\ No newline at end of file
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// An abstract endpoint where the proxy listens
/// </summary>
public abstract class ProxyEndPoint
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
protected ProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
{
IpAddress = ipAddress;
Port = port;
EnableSsl = enableSsl;
}
/// <summary>
/// underlying TCP Listener object
/// </summary>
internal TcpListener Listener { get; set; }
/// <summary>
/// Ip Address we are listening.
/// </summary>
public IPAddress IpAddress { get; }
/// <summary>
/// Port we are listening.
/// </summary>
public int Port { get; internal set; }
/// <summary>
/// Enable SSL?
/// </summary>
public bool EnableSsl { get; }
/// <summary>
/// Is IPv6 enabled?
/// </summary>
public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any)
|| Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None);
}
}
using System.Net;
namespace Titanium.Web.Proxy.Models
{
/// <summary>
/// A proxy end point client is not aware of
/// Usefull when requests are redirected to this proxy end point through port forwarding
/// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint
{
/// <summary>
/// Name of the Certificate need to be sent (same as the hostname we want to proxy)
/// This is valid only when UseServerNameIndication is set to false
/// </summary>
public string GenericCertificateName { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl) : base(ipAddress, port, enableSsl)
{
GenericCertificateName = "localhost";
}
}
}
\ No newline at end of file
......@@ -33,9 +33,9 @@ namespace Titanium.Web.Proxy.Network.Certificate
// Set this flag to true when exception detected to avoid further exceptions
private static bool doNotSetFriendlyName;
private readonly Action<Exception> exceptionFunc;
private readonly ExceptionHandler exceptionFunc;
internal BCCertificateMaker(Action<Exception> exceptionFunc)
internal BCCertificateMaker(ExceptionHandler exceptionFunc)
{
this.exceptionFunc = exceptionFunc;
}
......
......@@ -43,12 +43,12 @@ namespace Titanium.Web.Proxy.Network.Certificate
private object sharedPrivateKey;
private readonly Action<Exception> exceptionFunc;
private readonly ExceptionHandler exceptionFunc;
/// <summary>
/// Constructor.
/// </summary>
internal WinCertificateMaker(Action<Exception> exceptionFunc)
internal WinCertificateMaker(ExceptionHandler exceptionFunc)
{
this.exceptionFunc = exceptionFunc;
typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
......
......@@ -59,7 +59,7 @@ namespace Titanium.Web.Proxy.Network
private readonly ConcurrentDictionary<string, CachedCertificate> certificateCache;
private readonly ConcurrentDictionary<string, Task<X509Certificate2>> pendingCertificateCreationTasks;
private readonly Action<Exception> exceptionFunc;
private readonly ExceptionHandler exceptionFunc;
/// <summary>
/// Is the root certificate used by this proxy is valid?
......@@ -107,7 +107,9 @@ namespace Titanium.Web.Proxy.Network
if (certEngine == null)
{
certEngine = engine == CertificateEngine.BouncyCastle ? (ICertificateMaker)new BCCertificateMaker(exceptionFunc) : new WinCertificateMaker(exceptionFunc);
certEngine = engine == CertificateEngine.BouncyCastle
? (ICertificateMaker)new BCCertificateMaker(exceptionFunc)
: new WinCertificateMaker(exceptionFunc);
}
}
}
......@@ -196,9 +198,9 @@ namespace Titanium.Web.Proxy.Network
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true</param>
/// <param name="trustRootCertificateAsAdmin "></param>
/// <param name="trustRootCertificateAsAdmin"></param>
/// <param name="exceptionFunc"></param>
internal CertificateManager(string rootCertificateName, string rootCertificateIssuerName, bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin, Action < Exception> exceptionFunc)
internal CertificateManager(string rootCertificateName, string rootCertificateIssuerName, bool userTrustRootCertificate, bool machineTrustRootCertificate, bool trustRootCertificateAsAdmin, ExceptionHandler exceptionFunc)
{
this.exceptionFunc = exceptionFunc;
......@@ -310,6 +312,7 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// Make current machine trust the Root Certificate used by this proxy
/// </summary>
/// <param name="storeName"></param>
/// <param name="storeLocation"></param>
/// <returns></returns>
private void InstallCertificate(StoreName storeName, StoreLocation storeLocation)
......@@ -348,7 +351,9 @@ namespace Titanium.Web.Proxy.Network
/// <summary>
/// Remove the Root Certificate trust
/// </summary>
/// <param name="storeName"></param>
/// <param name="storeLocation"></param>
/// <param name="certificate"></param>
/// <returns></returns>
private void UninstallCertificate(StoreName storeName, StoreLocation storeLocation, X509Certificate2 certificate)
{
......
......@@ -15,7 +15,7 @@ namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
private async Task<bool> CheckAuthorization(HttpResponseWriter clientStreamWriter, SessionEventArgs session)
private async Task<bool> CheckAuthorization(SessionEventArgs session)
{
if (AuthenticateUserFunc == null)
{
......@@ -29,7 +29,7 @@ namespace Titanium.Web.Proxy
var header = httpHeaders.GetFirstHeader(KnownHeaders.ProxyAuthorization);
if (header == null)
{
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Required");
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Required");
return false;
}
......@@ -37,7 +37,7 @@ namespace Titanium.Web.Proxy
if (headerValueParts.Length != 2 || !headerValueParts[0].EqualsIgnoreCase(KnownHeaders.ProxyAuthorizationBasic))
{
//Return not authorized
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
return false;
}
......@@ -46,25 +46,32 @@ namespace Titanium.Web.Proxy
if (colonIndex == -1)
{
//Return not authorized
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
return false;
}
string username = decoded.Substring(0, colonIndex);
string password = decoded.Substring(colonIndex + 1);
return await AuthenticateUserFunc(username, password);
bool authenticated = await AuthenticateUserFunc(username, password);
if (!authenticated)
{
//Return not authorized
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
}
return authenticated;
}
catch (Exception e)
{
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders));
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", session, e, httpHeaders));
//Return not authorized
session.WebSession.Response = await SendAuthentication407Response(clientStreamWriter, "Proxy Authentication Invalid");
session.WebSession.Response = CreateAuthentication407Response("Proxy Authentication Invalid");
return false;
}
}
private async Task<Response> SendAuthentication407Response(HttpResponseWriter clientStreamWriter, string description)
private Response CreateAuthentication407Response(string description)
{
var response = new Response
{
......@@ -77,7 +84,6 @@ namespace Titanium.Web.Proxy
response.Headers.AddHeader(KnownHeaders.ProxyConnection, KnownHeaders.ProxyConnectionClose);
response.Headers.FixProxyHeaders();
await clientStreamWriter.WriteResponseAsync(response);
return response;
}
}
......
......@@ -53,12 +53,12 @@ namespace Titanium.Web.Proxy
/// <summary>
/// An default exception log func
/// </summary>
private readonly Lazy<Action<Exception>> defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { }));
private readonly Lazy<ExceptionHandler> defaultExceptionFunc = new Lazy<ExceptionHandler>(() => (e => { }));
/// <summary>
/// backing exception func for exposed public property
/// </summary>
private Action<Exception> exceptionFunc;
private ExceptionHandler exceptionFunc;
/// <summary>
/// Is the proxy currently running
......@@ -192,7 +192,7 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Callback for error events in proxy
/// </summary>
public Action<Exception> ExceptionFunc
public ExceptionHandler ExceptionFunc
{
get => exceptionFunc ?? defaultExceptionFunc.Value;
set => exceptionFunc = value;
......@@ -210,7 +210,7 @@ namespace Titanium.Web.Proxy
/// </summary>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true</param>
/// <param name="trustRootCertificateAsAdmin "></param>
/// <param name="trustRootCertificateAsAdmin"></param>
public ProxyServer(bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false) : this(null, null, userTrustRootCertificate, machineTrustRootCertificate, trustRootCertificateAsAdmin)
{
}
......@@ -222,7 +222,7 @@ namespace Titanium.Web.Proxy
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
/// <param name="userTrustRootCertificate"></param>
/// <param name="machineTrustRootCertificate">Note:setting machineTrustRootCertificate to true will force userTrustRootCertificate to true</param>
/// <param name="trustRootCertificateAsAdmin "></param>
/// <param name="trustRootCertificateAsAdmin"></param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName, bool userTrustRootCertificate = true, bool machineTrustRootCertificate = false, bool trustRootCertificateAsAdmin = false)
{
//default values
......@@ -660,6 +660,7 @@ namespace Titanium.Web.Proxy
endPoint.Listener.Server.Dispose();
}
internal void UpdateClientConnectionCount(bool increment)
{
if (increment)
......
......@@ -64,14 +64,6 @@ namespace Titanium.Web.Proxy
var httpRemoteUri = new Uri("http://" + httpUrl);
connectHostname = httpRemoteUri.Host;
//filter out excluded host names
bool excluded = false;
if(endPoint.BeforeTunnelConnect!=null)
{
excluded = await endPoint.BeforeTunnelConnect(connectHostname);
}
connectRequest = new ConnectRequest
{
RequestUri = httpRemoteUri,
......@@ -85,12 +77,17 @@ namespace Titanium.Web.Proxy
connectArgs.ProxyClient.TcpClient = tcpClient;
connectArgs.ProxyClient.ClientStream = clientStream;
await endPoint.InvokeTunnectConnectRequest(this, connectArgs, ExceptionFunc);
await endPoint.InvokeBeforeTunnelConnectRequest(this, connectArgs, ExceptionFunc);
//filter out excluded host names
bool excluded = connectArgs.Excluded;
if (await CheckAuthorization(clientStreamWriter, connectArgs) == false)
if (await CheckAuthorization(connectArgs) == false)
{
await endPoint.InvokeTunnectConnectResponse(this, connectArgs, ExceptionFunc);
await endPoint.InvokeBeforeTunnectConnectResponse(this, connectArgs, ExceptionFunc);
//send the response
await clientStreamWriter.WriteResponseAsync(connectArgs.WebSession.Response);
return;
}
......@@ -108,7 +105,7 @@ namespace Titanium.Web.Proxy
connectRequest.ClientHelloInfo = clientHelloInfo;
}
await endPoint.InvokeTunnectConnectResponse(this, connectArgs, ExceptionFunc, isClientHello);
await endPoint.InvokeBeforeTunnectConnectResponse(this, connectArgs, ExceptionFunc, isClientHello);
if (!excluded && isClientHello)
{
......@@ -134,8 +131,9 @@ namespace Titanium.Web.Proxy
clientStreamReader = new CustomBinaryReader(clientStream, BufferSize);
clientStreamWriter = new HttpResponseWriter(clientStream, BufferSize);
}
catch
catch(Exception e)
{
ExceptionFunc(new Exception($"Could'nt authenticate client '{connectHostname}' with fake certificate.", e));
sslStream?.Dispose();
return;
}
......@@ -239,13 +237,20 @@ namespace Titanium.Web.Proxy
var sslStream = new SslStream(clientStream);
clientStream = new CustomBufferedStream(sslStream, BufferSize);
string sniHostName = clientHelloInfo.GetServerName();
string sniHostName = clientHelloInfo.GetServerName() ?? endPoint.GenericCertificateName;
string certName = HttpHelper.GetWildCardDomainName(sniHostName ?? endPoint.GenericCertificateName);
string certName = HttpHelper.GetWildCardDomainName(sniHostName);
var certificate = await CertificateManager.CreateCertificateAsync(certName);
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
try
{
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, SslProtocols.Tls, false);
}
catch (Exception e)
{
ExceptionFunc(new Exception($"Could'nt authenticate client '{sniHostName}' with fake certificate.", e));
return;
}
}
//HTTPS server created - we can now decrypt the client's traffic
......@@ -350,8 +355,12 @@ namespace Titanium.Web.Proxy
args.ProxyClient.ClientStreamWriter = clientStreamWriter;
//proxy authorization check
if (!args.IsTransparent && httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false)
if (!args.IsTransparent && httpsConnectHostname == null && await CheckAuthorization(args) == false)
{
await InvokeBeforeResponse(args);
//send the response
await clientStreamWriter.WriteResponseAsync(args.WebSession.Response);
break;
}
......@@ -370,10 +379,7 @@ namespace Titanium.Web.Proxy
}
//If user requested interception do it
if (BeforeRequest != null)
{
await BeforeRequest.InvokeAsync(this, args, ExceptionFunc);
}
await InvokeBeforeRequest(args);
var response = args.WebSession.Response;
......@@ -426,9 +432,9 @@ namespace Titanium.Web.Proxy
}
//If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
if (!args.WebSession.Response.ResponseLocked)
{
await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
await InvokeBeforeResponse(args);
}
await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize,
......@@ -593,5 +599,13 @@ namespace Titanium.Web.Proxy
requestHeaders.FixProxyHeaders();
}
private async Task InvokeBeforeRequest(SessionEventArgs args)
{
if (BeforeRequest != null)
{
await BeforeRequest.InvokeAsync(this, args, ExceptionFunc);
}
}
}
}
......@@ -35,10 +35,10 @@ namespace Titanium.Web.Proxy
args.ReRequest = false;
//If user requested call back then do it
if (BeforeResponse != null && !response.ResponseLocked)
//if user requested call back then do it
if (!response.ResponseLocked)
{
await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
await InvokeBeforeResponse(args);
}
//if user requested to send request again
......@@ -125,5 +125,14 @@ namespace Titanium.Web.Proxy
var compressor = CompressionFactory.GetCompression(encodingType);
return await compressor.Compress(responseBodyStream);
}
private async Task InvokeBeforeResponse(SessionEventArgs args)
{
if (BeforeResponse != null)
{
await BeforeResponse.InvokeAsync(this, args, ExceptionFunc);
}
}
}
}
......@@ -13,7 +13,7 @@
<ItemGroup>
<PackageReference Include="Portable.BouncyCastle" Version="1.8.1.4" />
<PackageReference Include="StreamExtended" Version="1.0.135-beta" />
<PackageReference Include="StreamExtended" Version="1.0.138-beta" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
......
......@@ -14,7 +14,7 @@
<copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright>
<tags></tags>
<dependencies>
<dependency id="StreamExtended" version="1.0.110-beta" />
<dependency id="StreamExtended" version="1.0.138-beta" />
<dependency id="Portable.BouncyCastle" version="1.8.1.4" />
</dependencies>
</metadata>
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<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.138-beta" targetFramework="net45" />
</packages>
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment