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

External Proxy, Async & Server Certificate Validation #73

Merged
merged 12 commits into from
May 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Runtime.InteropServices;

namespace Titanium.Web.Proxy.Test
namespace Titanium.Web.Proxy.Examples.Basic
{
public class Program
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;

namespace Titanium.Web.Proxy.Test
namespace Titanium.Web.Proxy.Examples.Basic
{
public class ProxyTestController
{
public void StartProxy()
{
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;

//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
Expand All @@ -34,13 +36,14 @@ public void StartProxy()
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
//Please read about it before asking questions!
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
GenericCertificateName = "google.com"
};
ProxyServer.AddEndPoint(transparentEndPoint);

//ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };

foreach (var endPoint in ProxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
Expand All @@ -59,69 +62,80 @@ public void Stop()
ProxyServer.Stop();
}

//Test On Request, intecept requests
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e)
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.ProxySession.Request.Url);
Console.WriteLine(e.WebSession.Request.Url);

//read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders;
////read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;

if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT"))
if ((e.WebSession.Request.Method.ToUpper() == "POST" || e.WebSession.Request.Method.ToUpper() == "PUT"))
{
//Get/Set request body bytes
byte[] bodyBytes = e.GetRequestBody();
e.SetRequestBody(bodyBytes);
byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes);

//Get/Set request body as string
string bodyString = e.GetRequestBodyAsString();
e.SetRequestBodyString(bodyString);
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);

}

//To cancel a request with a custom HTML content
//Filter URL
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
//Redirect example
if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
e.Redirect("https://www.paypal.com");
await e.Redirect("https://www.paypal.com");
}
}

//Test script injection
//Insert script to read the Browser URL and send it back to proxy
public void OnResponse(object sender, SessionEventArgs e)
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{

//read response headers
var responseHeaders = e.ProxySession.Response.ResponseHeaders;
var responseHeaders = e.WebSession.Response.ResponseHeaders;

//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.RequestMethod == "GET" || e.RequestMethod == "POST")
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
if (e.ProxySession.Response.ResponseStatusCode == "200")
if (e.WebSession.Response.ResponseStatusCode == "200")
{
if (e.ProxySession.Response.ContentType.Trim().ToLower().Contains("text/html"))
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
byte[] bodyBytes = e.GetResponseBody();
e.SetResponseBody(bodyBytes);
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);

string body = e.GetResponseBodyAsString();
e.SetResponseBodyString(body);
string body = await e.GetResponseBodyAsString();
await e.SetResponseBodyString(body);
}
}
}
}

/// <summary>
/// Allows overriding default certificate validation logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public async 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.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<ProjectGuid>{F3B7E553-1904-4E80-BDC7-212342B5C952}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.Test</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Test</AssemblyName>
<RootNamespace>Titanium.Web.Proxy.Examples.Basic</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.Examples.Basic</AssemblyName>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
Expand Down Expand Up @@ -65,7 +65,7 @@
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
Expand Down
153 changes: 93 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A light weight http(s) proxy server written in C#

Kindly report only issues/bugs here . For programming help or questions use [StackOverflow](http://stackoverflow.com/questions/tagged/titanium-web-proxy) with the tag Titanium-Web-Proxy.

![alt tag](https://raw.githubusercontent.com/titanium007/Titanium/master/Titanium.Web.Proxy.Test/Capture.PNG)
![alt tag](https://raw.githubusercontent.com/justcoding121/Titanium-Web-Proxy/release/Examples/Titanium.Web.Proxy.Examples.Basic/Capture.PNG)

Features
========
Expand All @@ -32,102 +32,135 @@ After installing nuget package mark following files to be copied to app director
Setup HTTP proxy:

```csharp
// listen to client request & server response events
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;

var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true){
//Exclude Https addresses you don't want to proxy/cannot be proxied
//for example exclude dropbox client which use certificate pinning
ExcludedHttpsHostNameRegex = new List<string>() { "dropbox.com" }
};

//Add an explicit endpoint where the client is aware of the proxy
//So client would send request in a proxy friendly manner
ProxyServer.AddEndPoint(explicitEndPoint);
ProxyServer.Start();

//Only explicit proxies can be set as a system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint);

foreach (var endPoint in ProxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;

//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//for example dropbox.com
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
};

//An explicit endpoint is where the client knows about the existance of a proxy
//So client sends request in a proxy friendly manner
ProxyServer.AddEndPoint(explicitEndPoint);
ProxyServer.Start();


//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
//Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
GenericCertificateName = "google.com"
};
ProxyServer.AddEndPoint(transparentEndPoint);

//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
//ProxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//ProxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };

foreach (var endPoint in ProxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);

//Only explicit proxies can be set as system proxy!
ProxyServer.SetAsSystemHttpProxy(explicitEndPoint);
ProxyServer.SetAsSystemHttpsProxy(explicitEndPoint);

//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();

//Unsubscribe & Quit
ProxyServer.BeforeRequest -= OnRequest;
ProxyServer.BeforeResponse -= OnResponse;
ProxyServer.Stop();
//Unsubscribe & Quit
ProxyServer.BeforeRequest -= OnRequest;
ProxyServer.BeforeResponse -= OnResponse;
ProxyServer.Stop();

```
Sample request and response event handlers

```csharp

//Test On Request, intecept requests
//Read browser URL send back to proxy by the injection script in OnResponse event
public void OnRequest(object sender, SessionEventArgs e)
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.ProxySession.Request.Url);
Console.WriteLine(e.WebSession.Request.Url);

//read request headers
var requestHeaders = e.ProxySession.Request.RequestHeaders;
////read request headers
var requestHeaders = e.WebSession.Request.RequestHeaders;

if ((e.RequestMethod.ToUpper() == "POST" || e.RequestMethod.ToUpper() == "PUT"))
if ((e.WebSession.Request.Method.ToUpper() == "POST" || e.WebSession.Request.Method.ToUpper() == "PUT"))
{
//Get/Set request body bytes
byte[] bodyBytes = e.GetRequestBody();
e.SetRequestBody(bodyBytes);
byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes);

//Get/Set request body as string
string bodyString = e.GetRequestBodyAsString();
e.SetRequestBodyString(bodyString);
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);

}

//To cancel a request with a custom HTML content
//Filter URL

if (e.ProxySession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{
e.Ok("<!DOCTYPE html>"+
"<html><body><h1>"+
"Website Blocked"+
"</h1>"+
"<p>Blocked by titanium web proxy.</p>"+
"</body>"+
"</html>");
await e.Ok("<!DOCTYPE html>" +
"<html><body><h1>" +
"Website Blocked" +
"</h1>" +
"<p>Blocked by titanium web proxy.</p>" +
"</body>" +
"</html>");
}
//Redirect example
if (e.WebSession.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{
await e.Redirect("https://www.paypal.com");
}
}

//Test script injection
//Insert script to read the Browser URL and send it back to proxy
public void OnResponse(object sender, SessionEventArgs e)
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{

//read response headers
var responseHeaders = e.ProxySession.Response.ResponseHeaders;

var responseHeaders = e.WebSession.Response.ResponseHeaders;

if (e.RequestMethod == "GET" || e.RequestMethod == "POST")
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
if (e.WebSession.Request.Method == "GET" || e.WebSession.Request.Method == "POST")
{
if (e.ProxySession.Response.ResponseStatusCode == "200")
if (e.WebSession.Response.ResponseStatusCode == "200")
{
if (e.ProxySession.Response.ContentType.Trim().ToLower().Contains("text/html"))
if (e.WebSession.Response.ContentType!=null && e.WebSession.Response.ContentType.Trim().ToLower().Contains("text/html"))
{
string body = e.GetResponseBodyAsString();
byte[] bodyBytes = await e.GetResponseBody();
await e.SetResponseBody(bodyBytes);

string body = await e.GetResponseBodyAsString();
await e.SetResponseBodyString(body);
}
}
}
}


/// Allows overriding default certificate validation logic
public async 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.");
}
```
Future roadmap
============
* Add callbacks for client/server certificate validation/selection
* Support mutual authentication
* Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0
Expand Down
6 changes: 0 additions & 6 deletions Titanium.Web.Proxy.Test/Titanium.Web.Proxy.Test.csproj.user

This file was deleted.

Loading