Skip to content
This repository was archived by the owner on Jul 9, 2023. It is now read-only.

Commit 82326b0

Browse files
Merge pull request #82 from justcoding121/master
sync with master
2 parents e1cd5fc + 703f521 commit 82326b0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1617
-784
lines changed

Examples/Titanium.Web.Proxy.Examples.Basic/ProxyTestController.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public void StartProxy()
1515
ProxyServer.BeforeRequest += OnRequest;
1616
ProxyServer.BeforeResponse += OnResponse;
1717
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
18+
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
1819

1920
//Exclude Https addresses you don't want to proxy
2021
//Usefull for clients that use certificate pinning
@@ -129,13 +130,25 @@ public async Task OnResponse(object sender, SessionEventArgs e)
129130
/// </summary>
130131
/// <param name="sender"></param>
131132
/// <param name="e"></param>
132-
public async Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
133+
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
133134
{
134135
//set IsValid to true/false based on Certificate Errors
135136
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
136137
e.IsValid = true;
137-
else
138-
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");
138+
139+
return Task.FromResult(0);
140+
}
141+
142+
/// <summary>
143+
/// Allows overriding default client certificate selection logic during mutual authentication
144+
/// </summary>
145+
/// <param name="sender"></param>
146+
/// <param name="e"></param>
147+
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
148+
{
149+
//set e.clientCertificate to override
150+
151+
return Task.FromResult(0);
139152
}
140153
}
141154
}

README.md

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ Features
1212
========
1313

1414
* Supports Http(s) and most features of HTTP 1.1
15-
* Supports relaying of WebSockets
16-
* Supports script injection
15+
* Support redirect/block/update requests
16+
* Supports updating response
17+
* Safely relays WebSocket requests over Http
18+
* Support mutual SSL authentication
19+
* Fully asynchronous proxy
1720

1821
Usage
1922
=====
@@ -35,6 +38,8 @@ Setup HTTP proxy:
3538
ProxyServer.BeforeRequest += OnRequest;
3639
ProxyServer.BeforeResponse += OnResponse;
3740
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
41+
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
42+
3843

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

87-
```csharp
88-
89-
//intecept & cancel, redirect or update requests
92+
```csharp
9093
public async Task OnRequest(object sender, SessionEventArgs e)
9194
{
9295
Console.WriteLine(e.WebSession.Request.Url);
@@ -148,20 +151,27 @@ Sample request and response event handlers
148151
}
149152
}
150153

151-
152-
/// Allows overriding default certificate validation logic
153-
public async Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
154+
/// Allows overriding default certificate validation logic
155+
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
154156
{
155157
//set IsValid to true/false based on Certificate Errors
156158
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
157159
e.IsValid = true;
158-
else
159-
await e.Session.Ok("Cannot validate server certificate! Not safe to proceed.");
160+
161+
return Task.FromResult(0);
162+
}
163+
164+
/// Allows overriding default client certificate selection logic during mutual authentication
165+
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
166+
{
167+
//set e.clientCertificate to override
168+
169+
return Task.FromResult(0);
160170
}
161171
```
162172
Future roadmap
163173
============
164-
* Support mutual authentication
174+
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
165175
* Support Server Name Indication (SNI) for transparent endpoints
166176
* Support HTTP 2.0
167177

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System;
2+
using System.Linq;
3+
using System.Net.Security;
4+
using System.Security.Cryptography.X509Certificates;
5+
using System.Threading.Tasks;
6+
using Titanium.Web.Proxy.EventArguments;
7+
8+
namespace Titanium.Web.Proxy
9+
{
10+
public partial class ProxyServer
11+
{
12+
/// <summary>
13+
/// Call back to override server certificate validation
14+
/// </summary>
15+
/// <param name="sender"></param>
16+
/// <param name="certificate"></param>
17+
/// <param name="chain"></param>
18+
/// <param name="sslPolicyErrors"></param>
19+
/// <returns></returns>
20+
internal static bool ValidateServerCertificate(
21+
object sender,
22+
X509Certificate certificate,
23+
X509Chain chain,
24+
SslPolicyErrors sslPolicyErrors)
25+
{
26+
//if user callback is registered then do it
27+
if (ServerCertificateValidationCallback != null)
28+
{
29+
var args = new CertificateValidationEventArgs();
30+
31+
args.Certificate = certificate;
32+
args.Chain = chain;
33+
args.SslPolicyErrors = sslPolicyErrors;
34+
35+
36+
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
37+
Task[] handlerTasks = new Task[invocationList.Length];
38+
39+
for (int i = 0; i < invocationList.Length; i++)
40+
{
41+
handlerTasks[i] = ((Func<object, CertificateValidationEventArgs, Task>)invocationList[i])(null, args);
42+
}
43+
44+
Task.WhenAll(handlerTasks).Wait();
45+
46+
return args.IsValid;
47+
}
48+
49+
if (sslPolicyErrors == SslPolicyErrors.None)
50+
return true;
51+
52+
//By default
53+
//do not allow this client to communicate with unauthenticated servers.
54+
return false;
55+
}
56+
57+
/// <summary>
58+
/// Call back to select client certificate used for mutual authentication
59+
/// </summary>
60+
/// <param name="sender"></param>
61+
/// <param name="certificate"></param>
62+
/// <param name="chain"></param>
63+
/// <param name="sslPolicyErrors"></param>
64+
/// <returns></returns>
65+
internal static X509Certificate SelectClientCertificate(
66+
object sender,
67+
string targetHost,
68+
X509CertificateCollection localCertificates,
69+
X509Certificate remoteCertificate,
70+
string[] acceptableIssuers)
71+
{
72+
X509Certificate clientCertificate = null;
73+
var customSslStream = sender as SslStream;
74+
75+
if (acceptableIssuers != null &&
76+
acceptableIssuers.Length > 0 &&
77+
localCertificates != null &&
78+
localCertificates.Count > 0)
79+
{
80+
// Use the first certificate that is from an acceptable issuer.
81+
foreach (X509Certificate certificate in localCertificates)
82+
{
83+
string issuer = certificate.Issuer;
84+
if (Array.IndexOf(acceptableIssuers, issuer) != -1)
85+
clientCertificate = certificate;
86+
}
87+
}
88+
89+
if (localCertificates != null &&
90+
localCertificates.Count > 0)
91+
clientCertificate = localCertificates[0];
92+
93+
//If user call back is registered
94+
if (ClientCertificateSelectionCallback != null)
95+
{
96+
var args = new CertificateSelectionEventArgs();
97+
98+
args.targetHost = targetHost;
99+
args.localCertificates = localCertificates;
100+
args.remoteCertificate = remoteCertificate;
101+
args.acceptableIssuers = acceptableIssuers;
102+
args.clientCertificate = clientCertificate;
103+
104+
Delegate[] invocationList = ClientCertificateSelectionCallback.GetInvocationList();
105+
Task[] handlerTasks = new Task[invocationList.Length];
106+
107+
for (int i = 0; i < invocationList.Length; i++)
108+
{
109+
handlerTasks[i] = ((Func<object, CertificateSelectionEventArgs, Task>)invocationList[i])(null, args);
110+
}
111+
112+
Task.WhenAll(handlerTasks).Wait();
113+
114+
return args.clientCertificate;
115+
}
116+
117+
return clientCertificate;
118+
119+
}
120+
}
121+
}

Titanium.Web.Proxy/Compression/CompressionFactory.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
namespace Titanium.Web.Proxy.Compression
22
{
3-
class CompressionFactory
3+
/// <summary>
4+
/// A factory to generate the compression methods based on the type of compression
5+
/// </summary>
6+
internal class CompressionFactory
47
{
58
public ICompression Create(string type)
69
{

Titanium.Web.Proxy/Compression/DeflateCompression.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44

55
namespace Titanium.Web.Proxy.Compression
66
{
7-
class DeflateCompression : ICompression
7+
/// <summary>
8+
/// Concrete implementation of deflate compression
9+
/// </summary>
10+
internal class DeflateCompression : ICompression
811
{
912
public async Task<byte[]> Compress(byte[] responseBody)
1013
{
1114
using (var ms = new MemoryStream())
1215
{
1316
using (var zip = new DeflateStream(ms, CompressionMode.Compress, true))
1417
{
15-
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
18+
await zip.WriteAsync(responseBody, 0, responseBody.Length);
1619
}
1720

1821
return ms.ToArray();

Titanium.Web.Proxy/Compression/GZipCompression.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44

55
namespace Titanium.Web.Proxy.Compression
66
{
7-
class GZipCompression : ICompression
7+
/// <summary>
8+
/// concreate implementation of gzip compression
9+
/// </summary>
10+
internal class GZipCompression : ICompression
811
{
912
public async Task<byte[]> Compress(byte[] responseBody)
1013
{
1114
using (var ms = new MemoryStream())
1215
{
1316
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
1417
{
15-
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
18+
await zip.WriteAsync(responseBody, 0, responseBody.Length);
1619
}
1720

1821
return ms.ToArray();

Titanium.Web.Proxy/Compression/ICompression.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Titanium.Web.Proxy.Compression
44
{
5+
/// <summary>
6+
/// An inteface for http compression
7+
/// </summary>
58
interface ICompression
69
{
710
Task<byte[]> Compress(byte[] responseBody);

Titanium.Web.Proxy/Compression/ZlibCompression.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44

55
namespace Titanium.Web.Proxy.Compression
66
{
7-
class ZlibCompression : ICompression
7+
/// <summary>
8+
/// concrete implementation of zlib compression
9+
/// </summary>
10+
internal class ZlibCompression : ICompression
811
{
912
public async Task<byte[]> Compress(byte[] responseBody)
1013
{
1114
using (var ms = new MemoryStream())
1215
{
1316
using (var zip = new ZlibStream(ms, CompressionMode.Compress, true))
1417
{
15-
await zip.WriteAsync(responseBody, 0, responseBody.Length).ConfigureAwait(false);
18+
await zip.WriteAsync(responseBody, 0, responseBody.Length);
1619
}
1720

1821
return ms.ToArray();

Titanium.Web.Proxy/Decompression/DecompressionFactory.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
namespace Titanium.Web.Proxy.Decompression
22
{
3-
class DecompressionFactory
3+
/// <summary>
4+
/// A factory to generate the de-compression methods based on the type of compression
5+
/// </summary>
6+
internal class DecompressionFactory
47
{
5-
public IDecompression Create(string type)
8+
internal IDecompression Create(string type)
69
{
710
switch(type)
811
{

Titanium.Web.Proxy/Decompression/DefaultDecompression.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
namespace Titanium.Web.Proxy.Decompression
44
{
5-
class DefaultDecompression : IDecompression
5+
6+
/// <summary>
7+
/// When no compression is specified just return the byte array
8+
/// </summary>
9+
internal class DefaultDecompression : IDecompression
610
{
711
public Task<byte[]> Decompress(byte[] compressedArray)
812
{

Titanium.Web.Proxy/Decompression/DeflateDecompression.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,25 @@
55

66
namespace Titanium.Web.Proxy.Decompression
77
{
8-
class DeflateDecompression : IDecompression
8+
/// <summary>
9+
/// concrete implementation of deflate de-compression
10+
/// </summary>
11+
internal class DeflateDecompression : IDecompression
912
{
1013
public async Task<byte[]> Decompress(byte[] compressedArray)
1114
{
1215
var stream = new MemoryStream(compressedArray);
1316

1417
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
1518
{
16-
var buffer = new byte[Constants.BUFFER_SIZE];
19+
var buffer = new byte[ProxyConstants.BUFFER_SIZE];
1720

1821
using (var output = new MemoryStream())
1922
{
2023
int read;
21-
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
24+
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
2225
{
23-
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
26+
await output.WriteAsync(buffer, 0, read);
2427
}
2528

2629
return output.ToArray();

Titanium.Web.Proxy/Decompression/GZipDecompression.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,22 @@
55

66
namespace Titanium.Web.Proxy.Decompression
77
{
8-
class GZipDecompression : IDecompression
8+
/// <summary>
9+
/// concrete implementation of gzip de-compression
10+
/// </summary>
11+
internal class GZipDecompression : IDecompression
912
{
1013
public async Task<byte[]> Decompress(byte[] compressedArray)
1114
{
1215
using (var decompressor = new GZipStream(new MemoryStream(compressedArray), CompressionMode.Decompress))
1316
{
14-
var buffer = new byte[Constants.BUFFER_SIZE];
17+
var buffer = new byte[ProxyConstants.BUFFER_SIZE];
1518
using (var output = new MemoryStream())
1619
{
1720
int read;
18-
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
21+
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
1922
{
20-
await output.WriteAsync(buffer, 0, read).ConfigureAwait(false);
23+
await output.WriteAsync(buffer, 0, read);
2124
}
2225
return output.ToArray();
2326
}

0 commit comments

Comments
 (0)