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; }
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Security; using System.Net.Security;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication; using System.Security.Authentication;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
...@@ -20,6 +18,7 @@ using System.Threading.Tasks; ...@@ -20,6 +18,7 @@ using System.Threading.Tasks;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
partial class ProxyServer partial class ProxyServer
{ {
//This is called when client is aware of proxy //This is called when client is aware of proxy
...@@ -33,7 +32,6 @@ namespace Titanium.Web.Proxy ...@@ -33,7 +32,6 @@ namespace Titanium.Web.Proxy
try try
{ {
//read the first line HTTP command //read the first line HTTP command
var httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false); var httpCmd = await clientStreamReader.ReadLineAsync().ConfigureAwait(false);
if (string.IsNullOrEmpty(httpCmd)) if (string.IsNullOrEmpty(httpCmd))
...@@ -80,18 +78,28 @@ namespace Titanium.Web.Proxy ...@@ -80,18 +78,28 @@ namespace Titanium.Web.Proxy
try try
{ {
sslStream = new SslStream(clientStream, true); var connectRequest = new ConnectRequest() { Stream = clientStream, Uri = httpRemoteUri };
await TcpConnectionManager.GetClient(connectRequest, httpRemoteUri.Host, httpRemoteUri.Port, true, version).ConfigureAwait(false);
if (clientStream is SslStream)
{
sslStream = clientStream as SslStream;
}
else
{
sslStream = new SslStream(clientStream, true);
//Successfully managed to authenticate the client using the fake certificate //Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false, await sslStream.AuthenticateAsServerAsync(certificate, false,
Constants.SupportedProtocols, false).ConfigureAwait(false); Constants.SupportedProtocols, false).ConfigureAwait(false);
clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamWriter = new StreamWriter(sslStream);
//HTTPS server created - we can now decrypt the client's traffic //HTTPS server created - we can now decrypt the client's traffic
clientStream = sslStream; clientStream = sslStream;
} }
clientStreamReader = new CustomBinaryReader(sslStream);
clientStreamWriter = new StreamWriter(sslStream);
}
catch catch
{ {
if (sslStream != null) if (sslStream != null)
...@@ -226,7 +234,13 @@ namespace Titanium.Web.Proxy ...@@ -226,7 +234,13 @@ namespace Titanium.Web.Proxy
} }
var httpRemoteUri = new Uri(!isHttps ? httpCmdSplit[1] : (string.Concat("https://", args.WebSession.Request.Host, httpCmdSplit[1]))); var httpRemoteUri = new Uri(!isHttps ? httpCmdSplit[1] : (string.Concat("https://", args.WebSession.Request.Host, httpCmdSplit[1])));
#if DEBUG
if (httpRemoteUri.Host.Contains("localhost"))
{
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
break;
}
#endif
args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.RequestUri = httpRemoteUri;
args.WebSession.Request.Method = httpMethod; args.WebSession.Request.Method = httpMethod;
...@@ -263,8 +277,8 @@ namespace Titanium.Web.Proxy ...@@ -263,8 +277,8 @@ namespace Titanium.Web.Proxy
//construct the web request that we are going to issue on behalf of the client. //construct the web request that we are going to issue on behalf of the client.
connection = connection == null ? connection = connection == null ?
await TcpConnectionManager.GetClient(args, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version).ConfigureAwait(false) await TcpConnectionManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version).ConfigureAwait(false)
: lastRequest != args.WebSession.Request.RequestUri.Host ? await TcpConnectionManager.GetClient(args, args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version).ConfigureAwait(false) : lastRequest != args.WebSession.Request.RequestUri.Host ? await TcpConnectionManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version).ConfigureAwait(false)
: connection; : connection;
lastRequest = TcpConnectionManager.GetConnectionKey(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version); lastRequest = TcpConnectionManager.GetConnectionKey(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version);
...@@ -306,7 +320,7 @@ namespace Titanium.Web.Proxy ...@@ -306,7 +320,7 @@ namespace Titanium.Web.Proxy
//If request was modified by user //If request was modified by user
if (args.WebSession.Request.RequestBodyRead) if (args.WebSession.Request.RequestBodyRead)
{ {
if(args.WebSession.Request.ContentEncoding!=null) if (args.WebSession.Request.ContentEncoding != null)
{ {
args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody); args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
} }
...@@ -359,7 +373,7 @@ namespace Titanium.Web.Proxy ...@@ -359,7 +373,7 @@ namespace Titanium.Web.Proxy
private static async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion) private static async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion)
{ {
await clientStreamWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2}", httpVersion.Major, httpVersion.Minor,"200 Connection established")).ConfigureAwait(false); await clientStreamWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2}", httpVersion.Major, httpVersion.Minor, "200 Connection established")).ConfigureAwait(false);
await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now)).ConfigureAwait(false); await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now)).ConfigureAwait(false);
await clientStreamWriter.WriteLineAsync().ConfigureAwait(false); await clientStreamWriter.WriteLineAsync().ConfigureAwait(false);
await clientStreamWriter.FlushAsync().ConfigureAwait(false); await clientStreamWriter.FlushAsync().ConfigureAwait(false);
...@@ -446,13 +460,12 @@ namespace Titanium.Web.Proxy ...@@ -446,13 +460,12 @@ namespace Titanium.Web.Proxy
X509Chain chain, X509Chain chain,
SslPolicyErrors sslPolicyErrors) SslPolicyErrors sslPolicyErrors)
{ {
var param = sender as CustomSslStream; var customSslStream = sender as CustomSslStream;
if (ServerCertificateValidationCallback != null) if (ServerCertificateValidationCallback != null)
{ {
var args = new CertificateValidationEventArgs(); var args = new CertificateValidationEventArgs();
args.Session = param.Session;
args.Certificate = certificate; args.Certificate = certificate;
args.Chain = chain; args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors; args.SslPolicyErrors = sslPolicyErrors;
...@@ -468,11 +481,6 @@ namespace Titanium.Web.Proxy ...@@ -468,11 +481,6 @@ namespace Titanium.Web.Proxy
Task.WhenAll(handlerTasks).Wait(); Task.WhenAll(handlerTasks).Wait();
if (!args.IsValid)
{
param.Session.WebSession.Request.CancelRequest = true;
}
return args.IsValid; return args.IsValid;
} }
...@@ -484,6 +492,87 @@ namespace Titanium.Web.Proxy ...@@ -484,6 +492,87 @@ namespace Titanium.Web.Proxy
return false; 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 CustomSslStream;
if (customSslStream.Param is ConnectRequest && remoteCertificate != null)
{
var connectRequest = customSslStream.Param as ConnectRequest;
var sslStream = new SslStream(connectRequest.Stream, true);
var certificate = CertManager.CreateCertificate(connectRequest.Uri.Host, false).Result;
//Successfully managed to authenticate the client using the fake certificate
sslStream.AuthenticateAsServerAsync(certificate, true,
Constants.SupportedProtocols, false).Wait();
connectRequest.Stream = sslStream;
clientCertificate = sslStream.RemoteCertificate;
}
else if (customSslStream.Param is ConnectRequest)
{
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 (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;
}
} }
} }
\ No newline at end of file
...@@ -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