Skip to content
This repository was archived by the owner on Jul 9, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
aeb9f31
Merge pull request #68 from justcoding121/release
justcoding121 Apr 30, 2016
af254c3
Merge pull request #73 from justcoding121/release
justcoding121 May 21, 2016
9cfe6e7
build number fix
justcoding121 May 21, 2016
2b1ad86
Use Version instead of string object
justcoding121 May 23, 2016
6ee0225
update version
justcoding121 May 23, 2016
f38199d
fix build number
justcoding121 May 23, 2016
1849d31
optimize connection manager; fix asyn/await
justcoding121 May 27, 2016
167bb2f
Fix version parse
justcoding121 May 27, 2016
554069b
Fix bug caused by using wrong variable in SessionEventArgs
justcoding121 Jun 1, 2016
dfc6d9a
config await
justcoding121 Jun 1, 2016
e4dfa44
issue #13 Fix
justcoding121 Jun 3, 2016
c525dda
let users change timeout minutes
justcoding121 Jun 3, 2016
65b923f
Add mutual authentication capability
justcoding121 Jun 4, 2016
ed3ce01
remove caching which can cause errors
justcoding121 Jun 4, 2016
c3e4078
fix performance issues
justcoding121 Jun 4, 2016
a8e16d4
remove async
justcoding121 Jun 4, 2016
8211b54
connection perf
justcoding121 Jun 4, 2016
3a41b1d
fix proxy-connection
justcoding121 Jun 4, 2016
5d2536e
Fix async causing performance issues
justcoding121 Jun 4, 2016
f6aa591
release client
justcoding121 Jun 4, 2016
5051fd5
Expose client IP Address #77
justcoding121 Jun 11, 2016
af36ece
Expose both IP & port
justcoding121 Jun 11, 2016
3ef9595
EndPoint to IpEndPoint
justcoding121 Jun 11, 2016
8bc50e7
Fix bug in dispose param
justcoding121 Jun 11, 2016
a4e2e3e
comments
justcoding121 Jun 11, 2016
f59fb6a
clean up;add comments
justcoding121 Jun 16, 2016
6fb5f0b
Add comments
justcoding121 Jun 16, 2016
4bcd22f
undo test change
justcoding121 Jun 16, 2016
9bac69a
add more comments
justcoding121 Jun 16, 2016
7cf9be8
fix comment
justcoding121 Jun 16, 2016
3042d21
Move certificate handler
justcoding121 Jun 16, 2016
703f521
Merge pull request #80 from justcoding121/cleanup
justcoding121 Jun 16, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public void StartProxy()
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;

//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
Expand Down Expand Up @@ -129,13 +130,25 @@ public async Task OnResponse(object sender, SessionEventArgs e)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public async Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
else
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");

return Task.FromResult(0);
}

/// <summary>
/// Allows overriding default client certificate selection logic during mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override

return Task.FromResult(0);
}
}
}
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ Features
========

* Supports Http(s) and most features of HTTP 1.1
* Supports relaying of WebSockets
* Supports script injection
* Support redirect/block/update requests
* Supports updating response
* Safely relays WebSocket requests over Http
* Support mutual SSL authentication
* Fully asynchronous proxy

Usage
=====
Expand All @@ -35,6 +38,8 @@ Setup HTTP proxy:
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;


//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
Expand Down Expand Up @@ -84,9 +89,7 @@ Setup HTTP proxy:
```
Sample request and response event handlers

```csharp

//intecept & cancel, redirect or update requests
```csharp
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.WebSession.Request.Url);
Expand Down Expand Up @@ -148,20 +151,27 @@ Sample request and response event handlers
}
}


/// Allows overriding default certificate validation logic
public async Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
/// Allows overriding default certificate validation logic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
else
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");

return Task.FromResult(0);
}

/// Allows overriding default client certificate selection logic during mutual authentication
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
//set e.clientCertificate to override

return Task.FromResult(0);
}
```
Future roadmap
============
* Support mutual authentication
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
* Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0

121 changes: 121 additions & 0 deletions Titanium.Web.Proxy/CertificateHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;

namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
/// <summary>
/// Call back to override server certificate validation
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
//if user callback is registered then do it
if (ServerCertificateValidationCallback != null)
{
var args = new CertificateValidationEventArgs();

args.Certificate = certificate;
args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors;


Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];

for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>)invocationList[i])(null, args);
}

Task.WhenAll(handlerTasks).Wait();

return args.IsValid;
}

if (sslPolicyErrors == SslPolicyErrors.None)
return true;

//By default
//do not allow this client to communicate with unauthenticated servers.
return false;
}

/// <summary>
/// Call back to select client certificate used for mutual authentication
/// </summary>
/// <param name="sender"></param>
/// <param name="certificate"></param>
/// <param name="chain"></param>
/// <param name="sslPolicyErrors"></param>
/// <returns></returns>
internal static X509Certificate SelectClientCertificate(
object sender,
string targetHost,
X509CertificateCollection localCertificates,
X509Certificate remoteCertificate,
string[] acceptableIssuers)
{
X509Certificate clientCertificate = null;
var customSslStream = sender as SslStream;

if (acceptableIssuers != null &&
acceptableIssuers.Length > 0 &&
localCertificates != null &&
localCertificates.Count > 0)
{
// Use the first certificate that is from an acceptable issuer.
foreach (X509Certificate certificate in localCertificates)
{
string issuer = certificate.Issuer;
if (Array.IndexOf(acceptableIssuers, issuer) != -1)
clientCertificate = certificate;
}
}

if (localCertificates != null &&
localCertificates.Count > 0)
clientCertificate = localCertificates[0];

//If user call back is registered
if (ClientCertificateSelectionCallback != null)
{
var args = new CertificateSelectionEventArgs();

args.targetHost = targetHost;
args.localCertificates = localCertificates;
args.remoteCertificate = remoteCertificate;
args.acceptableIssuers = acceptableIssuers;
args.clientCertificate = clientCertificate;

Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];

for (int i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, CertificateSelectionEventArgs, Task>)invocationList[i])(null, args);
}

Task.WhenAll(handlerTasks).Wait();

return args.clientCertificate;
}

return clientCertificate;

}
}
}
5 changes: 4 additions & 1 deletion Titanium.Web.Proxy/Compression/CompressionFactory.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
namespace Titanium.Web.Proxy.Compression
{
class CompressionFactory
/// <summary>
/// A factory to generate the compression methods based on the type of compression
/// </summary>
internal class CompressionFactory
{
public ICompression Create(string type)
{
Expand Down
7 changes: 5 additions & 2 deletions Titanium.Web.Proxy/Compression/DeflateCompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

namespace Titanium.Web.Proxy.Compression
{
class DeflateCompression : ICompression
/// <summary>
/// Concrete implementation of deflate compression
/// </summary>
internal class DeflateCompression : ICompression
{
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
using (var zip = new DeflateStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}

return ms.ToArray();
Expand Down
7 changes: 5 additions & 2 deletions Titanium.Web.Proxy/Compression/GZipCompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

namespace Titanium.Web.Proxy.Compression
{
class GZipCompression : ICompression
/// <summary>
/// concreate implementation of gzip compression
/// </summary>
internal class GZipCompression : ICompression
{
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}

return ms.ToArray();
Expand Down
3 changes: 3 additions & 0 deletions Titanium.Web.Proxy/Compression/ICompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Titanium.Web.Proxy.Compression
{
/// <summary>
/// An inteface for http compression
/// </summary>
interface ICompression
{
Task<byte[]> Compress(byte[] responseBody);
Expand Down
7 changes: 5 additions & 2 deletions Titanium.Web.Proxy/Compression/ZlibCompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@

namespace Titanium.Web.Proxy.Compression
{
class ZlibCompression : ICompression
/// <summary>
/// concrete implementation of zlib compression
/// </summary>
internal class ZlibCompression : ICompression
{
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
using (var zip = new ZlibStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}

return ms.ToArray();
Expand Down
7 changes: 5 additions & 2 deletions Titanium.Web.Proxy/Decompression/DecompressionFactory.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace Titanium.Web.Proxy.Decompression
{
class DecompressionFactory
/// <summary>
/// A factory to generate the de-compression methods based on the type of compression
/// </summary>
internal class DecompressionFactory
{
public IDecompression Create(string type)
internal IDecompression Create(string type)
{
switch(type)
{
Expand Down
6 changes: 5 additions & 1 deletion Titanium.Web.Proxy/Decompression/DefaultDecompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

namespace Titanium.Web.Proxy.Decompression
{
class DefaultDecompression : IDecompression

/// <summary>
/// When no compression is specified just return the byte array
/// </summary>
internal class DefaultDecompression : IDecompression
{
public Task<byte[]> Decompress(byte[] compressedArray)
{
Expand Down
11 changes: 7 additions & 4 deletions Titanium.Web.Proxy/Decompression/DeflateDecompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@

namespace Titanium.Web.Proxy.Decompression
{
class DeflateDecompression : IDecompression
/// <summary>
/// concrete implementation of deflate de-compression
/// </summary>
internal class DeflateDecompression : IDecompression
{
public async Task<byte[]> Decompress(byte[] compressedArray)
{
var stream = new MemoryStream(compressedArray);

using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{
var buffer = new byte[Constants.BUFFER_SIZE];
var buffer = new byte[ProxyConstants.BUFFER_SIZE];

using (var output = new MemoryStream())
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
await output.WriteAsync(buffer, 0, read);
}

return output.ToArray();
Expand Down
11 changes: 7 additions & 4 deletions Titanium.Web.Proxy/Decompression/GZipDecompression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@

namespace Titanium.Web.Proxy.Decompression
{
class GZipDecompression : IDecompression
/// <summary>
/// concrete implementation of gzip de-compression
/// </summary>
internal class GZipDecompression : IDecompression
{
public async Task<byte[]> Decompress(byte[] compressedArray)
{
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
{
var buffer = new byte[Constants.BUFFER_SIZE];
var buffer = new byte[ProxyConstants.BUFFER_SIZE];
using (var output = new MemoryStream())
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
await output.WriteAsync(buffer, 0, read);
}
return output.ToArray();
}
Expand Down
Loading