Commit b62f9f14 authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #211 from justcoding121/develop

Beta release 
parents e2144f40 d0fc544f
......@@ -37,9 +37,15 @@ namespace Titanium.Web.Proxy.Examples.Basic
{
//Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example dropbox.com
//for example google.com and dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
//Include Https addresses you want to proxy (others will be excluded)
//for example github.com
// IncludedHttpsHostNameRegex = new List<string>() { "github.com" }
//You can set only one of the ExcludedHttpsHostNameRegex and IncludedHttpsHostNameRegex properties, otherwise ArgumentException will be thrown
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required by proxy clients
......
Titanium
========
A light weight http(s) proxy server written in C#
A light weight HTTP(S) proxy server written in C#
![Build Status](https://ci.appveyor.com/api/projects/status/rvlxv8xgj0m7lkr4?svg=true)
......@@ -11,10 +11,10 @@ Kindly report only issues/bugs here . For programming help or questions use [Sta
Features
========
* Supports Http(s) and most features of HTTP 1.1
* Supports HTTP(S) and most features of HTTP 1.1
* Support redirect/block/update requests
* Supports updating response
* Safely relays WebSocket requests over Http
* Safely relays Web Socket requests over HTTP
* Support mutual SSL authentication
* Fully asynchronous proxy
* Supports proxy authentication
......@@ -23,7 +23,7 @@ Features
Usage
=====
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage.
Refer the HTTP Proxy Server library in your project, look up Test project to learn usage. ([Wiki & Contribution guidelines](https://github.com/justcoding121/Titanium-Web-Proxy/wiki))
Install by nuget:
......@@ -55,24 +55,24 @@ proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//Exclude HTTPS addresses you don't want to proxy
//Useful for clients that use certificate pinning
//for example dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Usefull when certificate trust is not requiered by proxy clients
//Use self-issued generic certificate on all HTTPS requests
//Optimizes performance by not creating a certificate for each HTTPS-enabled domain
//Useful when certificate trust is not required by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//An explicit endpoint is where the client knows about the existance of a proxy
//An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
//Warning! Transparent endpoint is not tested end to end
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
//Transparent endpoint is useful for reverse proxy (client is not aware of the existence 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
......@@ -110,6 +110,10 @@ proxyServer.Stop();
Sample request and response event handlers
```csharp
//To access requestBody from OnResponse handler
private Dictionary<Guid, string> requestBodyHistory;
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.WebSession.Request.Url);
......@@ -127,7 +131,10 @@ public async Task OnRequest(object sender, SessionEventArgs e)
//Get/Set request body as string
string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString);
//store request Body/request headers etc with request Id as key
//so that you can find it from response handler using request Id
requestBodyHistory[e.Id] = bodyString;
}
//To cancel a request with a custom HTML content
......@@ -170,6 +177,12 @@ public async Task OnResponse(object sender, SessionEventArgs e)
}
}
}
//access request body/request headers etc by looking up using requestId
if(requestBodyHistory.ContainsKey(e.Id))
{
var requestBody = requestBodyHistory[e.Id];
}
}
/// Allows overriding default certificate validation logic
......@@ -189,7 +202,7 @@ public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs
return Task.FromResult(0);
}
```
Future roadmap
Future road map (Pull requests are welcome!)
============
* Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0
......
......@@ -125,7 +125,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// Removes all types of proxy settings (both http & https)
/// Removes all types of proxy settings (both http and https)
/// </summary>
internal void DisableAllProxy()
{
......
......@@ -42,7 +42,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366921.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpTable
......@@ -52,7 +52,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct TcpRow
......@@ -72,7 +72,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa365928.aspx"/>
/// </summary>
[DllImport("iphlpapi.dll", SetLastError = true)]
internal static extern uint GetExtendedTcpTable(IntPtr tcpTable, ref int size, bool sort, int ipVersion, int tableClass, int reserved);
......@@ -124,7 +124,7 @@ namespace Titanium.Web.Proxy.Helpers
}
/// <summary>
/// relays the input clientStream to the server at the specified host name & port with the given httpCmd & headers as prefix
/// relays the input clientStream to the server at the specified host name and port with the given httpCmd and headers as prefix
/// Usefull for websocket requests
/// </summary>
/// <param name="bufferSize"></param>
......
......@@ -66,7 +66,7 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Prepare & send the http(s) request
/// Prepare and send the http(s) request
/// </summary>
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour)
......@@ -148,7 +148,7 @@ namespace Titanium.Web.Proxy.Http
}
/// <summary>
/// Receive & parse the http response from server
/// Receive and parse the http response from server
/// </summary>
/// <returns></returns>
internal async Task ReceiveResponse()
......
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
......@@ -13,14 +14,14 @@ namespace Titanium.Web.Proxy.Models
/// <summary>
/// Constructor.
/// </summary>
/// <param name="IpAddress"></param>
/// <param name="Port"></param>
/// <param name="EnableSsl"></param>
protected ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
protected ProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
{
this.IpAddress = IpAddress;
this.Port = Port;
this.EnableSsl = EnableSsl;
this.IpAddress = ipAddress;
this.Port = port;
this.EnableSsl = enableSsl;
}
/// <summary>
......@@ -43,7 +44,7 @@ namespace Titanium.Web.Proxy.Models
|| Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None);
internal TcpListener listener { get; set; }
internal TcpListener Listener { get; set; }
}
/// <summary>
......@@ -52,13 +53,46 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class ExplicitProxyEndPoint : ProxyEndPoint
{
private List<string> _excludedHttpsHostNameRegex;
private List<string> _includedHttpsHostNameRegex;
internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; }
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
public List<string> ExcludedHttpsHostNameRegex { get; set; }
public List<string> ExcludedHttpsHostNameRegex
{
get { return _excludedHttpsHostNameRegex; }
set
{
if (IncludedHttpsHostNameRegex != null)
{
throw new ArgumentException("Cannot set excluded when included is set");
}
_excludedHttpsHostNameRegex = value;
}
}
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
public List<string> IncludedHttpsHostNameRegex
{
get { return _includedHttpsHostNameRegex; }
set
{
if (ExcludedHttpsHostNameRegex != null)
{
throw new ArgumentException("Cannot set included when excluded is set");
}
_includedHttpsHostNameRegex = value;
}
}
/// <summary>
/// Generic certificate to use for SSL decryption.
......@@ -68,11 +102,11 @@ namespace Titanium.Web.Proxy.Models
/// <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)
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
public ExplicitProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
: base(ipAddress, port, enableSsl)
{
}
......@@ -94,11 +128,11 @@ namespace Titanium.Web.Proxy.Models
/// <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)
/// <param name="ipAddress"></param>
/// <param name="port"></param>
/// <param name="enableSsl"></param>
public TransparentProxyEndPoint(IPAddress ipAddress, int port, bool enableSsl)
: base(ipAddress, port, enableSsl)
{
this.GenericCertificateName = "localhost";
}
......
......@@ -48,6 +48,7 @@ namespace Titanium.Web.Proxy.Network.Certificate
/// <param name="keyStrength">The key strength.</param>
/// <param name="signatureAlgorithm">The signature algorithm.</param>
/// <param name="issuerPrivateKey">The issuer private key.</param>
/// <param name="hostName">The host name</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="PemException">Malformed sequence in RSA private key</exception>
private static X509Certificate2 GenerateCertificate(string hostName,
......
......@@ -34,6 +34,7 @@ namespace Titanium.Web.Proxy.Network
private readonly ICertificateMaker certEngine;
private bool clearCertificates { get; set; }
/// <summary>
/// Cache dictionary
/// </summary>
......@@ -42,9 +43,10 @@ namespace Titanium.Web.Proxy.Network
private readonly Action<Exception> exceptionFunc;
internal string Issuer { get; }
internal string RootCertificateName { get; }
internal X509Certificate2 rootCertificate { get; set; }
internal X509Certificate2 RootCertificate { get; set; }
internal CertificateManager(CertificateEngine engine,
string issuer,
......@@ -70,17 +72,29 @@ namespace Titanium.Web.Proxy.Network
certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
}
internal X509Certificate2 GetRootCertificate()
private string GetRootCertificatePath()
{
var path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
var assemblyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
// dynamically loaded assemblies returns string.Empty location
if (assemblyLocation == string.Empty)
{
assemblyLocation = System.Reflection.Assembly.GetEntryAssembly().Location;
}
var path = Path.GetDirectoryName(assemblyLocation);
if (null == path) throw new NullReferenceException();
var fileName = Path.Combine(path, "rootCert.pfx");
return fileName;
}
internal X509Certificate2 GetRootCertificate()
{
var fileName = GetRootCertificatePath();
if (!File.Exists(fileName)) return null;
try
{
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception e)
{
......@@ -94,35 +108,32 @@ namespace Titanium.Web.Proxy.Network
/// <returns>true if succeeded, else false</returns>
internal bool CreateTrustedRootCertificate()
{
rootCertificate = GetRootCertificate();
if (rootCertificate != null)
RootCertificate = GetRootCertificate();
if (RootCertificate != null)
{
return true;
}
try
{
rootCertificate = CreateCertificate(RootCertificateName, true);
RootCertificate = CreateCertificate(RootCertificateName, true);
}
catch (Exception e)
{
exceptionFunc(e);
}
if (rootCertificate != null)
if (RootCertificate != null)
{
try
{
var path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (null == path) throw new NullReferenceException();
var fileName = Path.Combine(path, "rootCert.pfx");
File.WriteAllBytes(fileName, rootCertificate.Export(X509ContentType.Pkcs12));
var fileName = GetRootCertificatePath();
File.WriteAllBytes(fileName, RootCertificate.Export(X509ContentType.Pkcs12));
}
catch (Exception e)
{
exceptionFunc(e);
}
}
return rootCertificate != null;
return RootCertificate != null;
}
/// <summary>
......@@ -149,7 +160,7 @@ namespace Titanium.Web.Proxy.Network
{
try
{
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, rootCertificate);
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, RootCertificate);
}
catch (Exception e)
{
......@@ -216,7 +227,7 @@ namespace Titanium.Web.Proxy.Network
internal void TrustRootCertificate(StoreLocation storeLocation,
Action<Exception> exceptionFunc)
{
if (rootCertificate == null)
if (RootCertificate == null)
{
exceptionFunc(
new Exception("Could not set root certificate"
......@@ -233,8 +244,8 @@ namespace Titanium.Web.Proxy.Network
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
x509RootStore.Add(rootCertificate);
x509PersonalStore.Add(rootCertificate);
x509RootStore.Add(RootCertificate);
x509PersonalStore.Add(RootCertificate);
}
catch (Exception e)
{
......
......@@ -7,7 +7,7 @@ using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// An object that holds TcpConnection to a particular server & port
/// An object that holds TcpConnection to a particular server and port
/// </summary>
public class TcpConnection : IDisposable
{
......
......@@ -5,7 +5,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
/// <summary>
/// Represents a managed interface of IP Helper API TcpRow struct
/// <see cref="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// <see href="http://msdn2.microsoft.com/en-us/library/aa366913.aspx"/>
/// </summary>
internal class TcpRow
{
......
......@@ -13,14 +13,14 @@ namespace Titanium.Web.Proxy
public partial class ProxyServer
{
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> Headers)
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> headers)
{
if (AuthenticateUserFunc == null)
{
return true;
}
var httpHeaders = Headers as HttpHeader[] ?? Headers.ToArray();
var httpHeaders = headers as HttpHeader[] ?? headers.ToArray();
try
{
......
......@@ -491,12 +491,12 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param>
private void Listen(ProxyEndPoint endPoint)
{
endPoint.listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.listener.Start();
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.Listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.listener.LocalEndpoint).Port;
endPoint.Port = ((IPEndPoint)endPoint.Listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
/// <summary>
......@@ -505,9 +505,9 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param>
private void QuitListen(ProxyEndPoint endPoint)
{
endPoint.listener.Stop();
endPoint.listener.Server.Close();
endPoint.listener.Server.Dispose();
endPoint.Listener.Stop();
endPoint.Listener.Server.Close();
endPoint.Listener.Server.Dispose();
}
/// <summary>
......@@ -541,7 +541,7 @@ namespace Titanium.Web.Proxy
try
{
//based on end point type call appropriate request handlers
tcpClient = endPoint.listener.EndAcceptTcpClient(asyn);
tcpClient = endPoint.Listener.EndAcceptTcpClient(asyn);
}
catch (ObjectDisposedException)
{
......@@ -595,7 +595,7 @@ namespace Titanium.Web.Proxy
}
// Get the listener that handles the client request.
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
endPoint.Listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
/// <summary>
......
......@@ -54,9 +54,9 @@ namespace Titanium.Web.Proxy
var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);
//Find the request Verb
var httpVerb = httpCmdSplit[0];
var httpVerb = httpCmdSplit[0].ToUpper();
httpRemoteUri = httpVerb.ToUpper() == "CONNECT" ?
httpRemoteUri = httpVerb == "CONNECT" ?
new Uri("http://" + httpCmdSplit[1]) : new Uri(httpCmdSplit[1]);
//parse the HTTP version
......@@ -72,13 +72,22 @@ namespace Titanium.Web.Proxy
}
//filter out excluded host names
var excluded = endPoint.ExcludedHttpsHostNameRegex != null
&& endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x));
bool excluded = false;
if (endPoint.ExcludedHttpsHostNameRegex != null)
{
excluded = endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x));
}
if (endPoint.IncludedHttpsHostNameRegex != null)
{
excluded = !endPoint.IncludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x));
}
List<HttpHeader> connectRequestHeaders = null;
//Client wants to create a secure tcp tunnel (its a HTTPS request)
if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
if (httpVerb == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
{
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
string tmpLine;
......@@ -131,7 +140,7 @@ namespace Titanium.Web.Proxy
}
//Sorry cannot do a HTTPS request decrypt to port 80 at this time
else if (httpVerb.ToUpper() == "CONNECT")
else if (httpVerb == "CONNECT")
{
//Cyphen out CONNECT request headers
await clientStreamReader.ReadAllLinesAsync();
......@@ -139,7 +148,7 @@ namespace Titanium.Web.Proxy
await WriteConnectResponse(clientStreamWriter, version);
await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, version, null,
null, version, null,
false, SupportedSslProtocols,
ValidateServerCertificate,
SelectClientCertificate,
......@@ -212,7 +221,7 @@ namespace Titanium.Web.Proxy
endPoint.EnableSsl ? endPoint.GenericCertificateName : null, endPoint, null);
}
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool CloseConnection)
private async Task HandleHttpSessionRequestInternal(TcpConnection connection, SessionEventArgs args, ExternalProxy customUpStreamHttpProxy, ExternalProxy customUpStreamHttpsProxy, bool closeConnection)
{
try
{
......@@ -331,7 +340,7 @@ namespace Titanium.Web.Proxy
return;
}
if (CloseConnection)
if (closeConnection)
{
//dispose
connection?.Dispose();
......
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