Commit 65b923fb authored by justcoding121's avatar justcoding121

Add mutual authentication capability

parent c525dda7
...@@ -15,6 +15,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -15,6 +15,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
ProxyServer.BeforeRequest += OnRequest; ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation; ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//Exclude Https addresses you don't want to proxy //Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning //Usefull for clients that use certificate pinning
...@@ -129,13 +130,25 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -129,13 +130,25 @@ namespace Titanium.Web.Proxy.Examples.Basic
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="e"></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 //set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None) if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true; 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);
} }
} }
} }
\ No newline at end of file
...@@ -12,8 +12,11 @@ Features ...@@ -12,8 +12,11 @@ Features
======== ========
* Supports Http(s) and most features of HTTP 1.1 * Supports Http(s) and most features of HTTP 1.1
* Supports relaying of WebSockets * Support redirect/block/update requests
* Supports script injection * Supports updating response
* Safely relays WebSocket requests over Http
* Support mutual SSL authentication
* Fully asynchronous proxy
Usage Usage
===== =====
...@@ -35,6 +38,8 @@ Setup HTTP proxy: ...@@ -35,6 +38,8 @@ Setup HTTP proxy:
ProxyServer.BeforeRequest += OnRequest; ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse; ProxyServer.BeforeResponse += OnResponse;
ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation; ProxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
ProxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
//Exclude Https addresses you don't want to proxy //Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning //Usefull for clients that use certificate pinning
...@@ -85,8 +90,6 @@ Setup HTTP proxy: ...@@ -85,8 +90,6 @@ Setup HTTP proxy:
Sample request and response event handlers Sample request and response event handlers
```csharp ```csharp
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e) public async Task OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine(e.WebSession.Request.Url); Console.WriteLine(e.WebSession.Request.Url);
...@@ -148,20 +151,27 @@ Sample request and response event handlers ...@@ -148,20 +151,27 @@ Sample request and response event handlers
} }
} }
/// Allows overriding default certificate validation logic /// Allows overriding default certificate validation logic
public async Task OnCertificateValidation(object sender, CertificateValidationEventArgs e) public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{ {
//set IsValid to true/false based on Certificate Errors //set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None) if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true; 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 Future roadmap
============ ============
* Support mutual authentication * Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
* Support Server Name Indication (SNI) for transparent endpoints * Support Server Name Indication (SNI) for transparent endpoints
* Support HTTP 2.0 * Support HTTP 2.0
using System;
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.EventArguments
{
public class CertificateSelectionEventArgs : EventArgs, IDisposable
{
public object sender { get; internal set; }
public string targetHost { get; internal set; }
public X509CertificateCollection localCertificates { get; internal set; }
public X509Certificate remoteCertificate { get; internal set; }
public string[] acceptableIssuers { get; internal set; }
public X509Certificate clientCertificate { get; set; }
public void Dispose()
{
throw new NotImplementedException();
}
}
}
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security; using System.Net.Security;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.EventArguments namespace Titanium.Web.Proxy.EventArguments
{ {
public class CertificateValidationEventArgs : EventArgs, IDisposable public class CertificateValidationEventArgs : EventArgs, IDisposable
{ {
public string HostName => Session.WebSession.Request.Host;
public SessionEventArgs Session { get; internal set; }
public X509Certificate Certificate { get; internal set; } public X509Certificate Certificate { get; internal set; }
public X509Chain Chain { get; internal set; } public X509Chain Chain { get; internal set; }
public SslPolicyErrors SslPolicyErrors { get; internal set; } public SslPolicyErrors SslPolicyErrors { get; internal set; }
......
...@@ -97,6 +97,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -97,6 +97,7 @@ namespace Titanium.Web.Proxy.Helpers
cached.LastAccess = DateTime.Now; cached.LastAccess = DateTime.Now;
return cached.Certificate; return cached.Certificate;
} }
X509Certificate2 certificate = null; X509Certificate2 certificate = null;
store.Open(OpenFlags.ReadWrite); store.Open(OpenFlags.ReadWrite);
string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer); string certificateSubject = string.Format("CN={0}, O={1}", certificateName, Issuer);
......
using System;
using System.IO;
namespace Titanium.Web.Proxy.Models
{
internal class ConnectRequest
{
internal Stream Stream { get; set; }
internal Uri Uri { get; set; }
}
}
...@@ -17,10 +17,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -17,10 +17,10 @@ namespace Titanium.Web.Proxy.Network
/// <summary> /// <summary>
/// Holds the current session /// Holds the current session
/// </summary> /// </summary>
internal SessionEventArgs Session { get; set; } internal object Param { get; set; }
internal CustomSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback) internal CustomSslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback, LocalCertificateSelectionCallback clientCertificateSelectionCallback)
:base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback) :base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback, clientCertificateSelectionCallback)
{ {
} }
......
...@@ -12,6 +12,7 @@ using Titanium.Web.Proxy.Extensions; ...@@ -12,6 +12,7 @@ using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
...@@ -38,7 +39,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -38,7 +39,12 @@ namespace Titanium.Web.Proxy.Network
{ {
static Dictionary<string, List<TcpConnection>> connectionCache = new Dictionary<string, List<TcpConnection>>(); static Dictionary<string, List<TcpConnection>> connectionCache = new Dictionary<string, List<TcpConnection>>();
static SemaphoreSlim connectionAccessLock = new SemaphoreSlim(1); static SemaphoreSlim connectionAccessLock = new SemaphoreSlim(1);
internal static async Task<TcpConnection> GetClient(SessionEventArgs sessionArgs, string hostname, int port, bool isHttps, Version version)
internal static async Task<TcpConnection> GetClient(string hostname, int port, bool isHttps, Version version)
{
return await GetClient(null, hostname, port, isHttps, version);
}
internal static async Task<TcpConnection> GetClient(ConnectRequest connectRequest, string hostname, int port, bool isHttps, Version version)
{ {
List<TcpConnection> cachedConnections = null; List<TcpConnection> cachedConnections = null;
TcpConnection cached = null; TcpConnection cached = null;
...@@ -72,13 +78,13 @@ namespace Titanium.Web.Proxy.Network ...@@ -72,13 +78,13 @@ namespace Titanium.Web.Proxy.Network
} }
if (cached == null) if (cached == null)
cached = await CreateClient(sessionArgs, hostname, port, isHttps, version).ConfigureAwait(false); cached = await CreateClient(connectRequest, hostname, port, isHttps, version).ConfigureAwait(false);
//just create one more preemptively //just create one more preemptively
if (cachedConnections == null || cachedConnections.Count() < 2) if (cachedConnections == null || cachedConnections.Count() < 2)
{ {
var task = CreateClient(sessionArgs, hostname, port, isHttps, version) var task = CreateClient(connectRequest, hostname, port, isHttps, version)
.ContinueWith(async (x) => { if (x.Status == TaskStatus.RanToCompletion) await ReleaseClient(x.Result); }); .ContinueWith(async (x) => { if (x.Status == TaskStatus.RanToCompletion) await ReleaseClient(x.Result); });
} }
...@@ -90,7 +96,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -90,7 +96,7 @@ namespace Titanium.Web.Proxy.Network
return string.Format("{0}:{1}:{2}:{3}:{4}", hostname.ToLower(), port, isHttps, version.Major, version.Minor); return string.Format("{0}:{1}:{2}:{3}:{4}", hostname.ToLower(), port, isHttps, version.Major, version.Minor);
} }
private static async Task<TcpConnection> CreateClient(SessionEventArgs sessionArgs, string hostname, int port, bool isHttps, Version version) private static async Task<TcpConnection> CreateClient(ConnectRequest connectRequest, string hostname, int port, bool isHttps, Version version)
{ {
TcpClient client; TcpClient client;
Stream stream; Stream stream;
...@@ -106,8 +112,8 @@ namespace Titanium.Web.Proxy.Network ...@@ -106,8 +112,8 @@ namespace Titanium.Web.Proxy.Network
using (var writer = new StreamWriter(stream, Encoding.ASCII, Constants.BUFFER_SIZE, true)) using (var writer = new StreamWriter(stream, Encoding.ASCII, Constants.BUFFER_SIZE, true))
{ {
await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port, sessionArgs.WebSession.Request.HttpVersion)).ConfigureAwait(false); await writer.WriteLineAsync(string.Format("CONNECT {0}:{1} {2}", hostname, port, version)).ConfigureAwait(false);
await writer.WriteLineAsync(string.Format("Host: {0}:{1}", sessionArgs.WebSession.Request.RequestUri.Host, sessionArgs.WebSession.Request.RequestUri.Port)).ConfigureAwait(false); await writer.WriteLineAsync(string.Format("Host: {0}:{1}", hostname, port)).ConfigureAwait(false);
await writer.WriteLineAsync("Connection: Keep-Alive").ConfigureAwait(false); await writer.WriteLineAsync("Connection: Keep-Alive").ConfigureAwait(false);
await writer.WriteLineAsync().ConfigureAwait(false); await writer.WriteLineAsync().ConfigureAwait(false);
await writer.FlushAsync().ConfigureAwait(false); await writer.FlushAsync().ConfigureAwait(false);
...@@ -132,8 +138,9 @@ namespace Titanium.Web.Proxy.Network ...@@ -132,8 +138,9 @@ namespace Titanium.Web.Proxy.Network
try try
{ {
sslStream = new CustomSslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate)); sslStream = new CustomSslStream(stream, true, new RemoteCertificateValidationCallback(ProxyServer.ValidateServerCertificate),
sslStream.Session = sessionArgs; new LocalCertificateSelectionCallback(ProxyServer.SelectClientCertificate));
sslStream.Param = connectRequest;
await sslStream.AuthenticateAsClientAsync(hostname, null, Constants.SupportedProtocols, false).ConfigureAwait(false); await sslStream.AuthenticateAsClientAsync(hostname, null, Constants.SupportedProtocols, false).ConfigureAwait(false);
stream = (Stream)sslStream; stream = (Stream)sslStream;
} }
......
...@@ -45,7 +45,16 @@ namespace Titanium.Web.Proxy ...@@ -45,7 +45,16 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public static int CertificateCacheTimeOutMinutes { get; set; } public static int CertificateCacheTimeOutMinutes { get; set; }
/// <summary>
/// Intercept request to server
/// </summary>
public static event Func<object, SessionEventArgs, Task> BeforeRequest; public static event Func<object, SessionEventArgs, Task> BeforeRequest;
/// <summary>
/// Intercept response from server
/// </summary>
public static event Func<object, SessionEventArgs, Task> BeforeResponse; public static event Func<object, SessionEventArgs, Task> BeforeResponse;
/// <summary> /// <summary>
...@@ -63,6 +72,10 @@ namespace Titanium.Web.Proxy ...@@ -63,6 +72,10 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public static event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback; public static event Func<object, CertificateValidationEventArgs, Task> ServerCertificateValidationCallback;
/// <summary>
/// Callback tooverride client certificate during SSL mutual authentication
/// </summary>
public static event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback;
public static List<ProxyEndPoint> ProxyEndPoints { get; set; } public static List<ProxyEndPoint> ProxyEndPoints { get; set; }
......
This diff is collapsed.
...@@ -60,7 +60,9 @@ ...@@ -60,7 +60,9 @@
<Compile Include="Decompression\GZipDecompression.cs" /> <Compile Include="Decompression\GZipDecompression.cs" />
<Compile Include="Decompression\IDecompression.cs" /> <Compile Include="Decompression\IDecompression.cs" />
<Compile Include="Decompression\ZlibDecompression.cs" /> <Compile Include="Decompression\ZlibDecompression.cs" />
<Compile Include="EventArguments\CertificateSelectionEventArgs.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" /> <Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Models\ConnectRequest.cs" />
<Compile Include="Network\ProxyClient.cs" /> <Compile Include="Network\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" /> <Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" /> <Compile Include="Extensions\HttpWebResponseExtensions.cs" />
......
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