Commit e170b5ae authored by Hakan Arıcı's avatar Hakan Arıcı Committed by GitHub

Merge pull request #3 from justcoding121/release

Sync
parents 5026d40a 5ecc3c81
...@@ -13,6 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -13,6 +13,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
public ProxyTestController() public ProxyTestController()
{ {
proxyServer = new ProxyServer(); proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
} }
public void StartProxy() public void StartProxy()
......
...@@ -17,6 +17,8 @@ Features ...@@ -17,6 +17,8 @@ Features
* Safely relays WebSocket requests over Http * Safely relays WebSocket requests over Http
* Support mutual SSL authentication * Support mutual SSL authentication
* Fully asynchronous proxy * Fully asynchronous proxy
* Supports proxy authentication
Usage Usage
===== =====
...@@ -25,18 +27,17 @@ Refer the HTTP Proxy Server library in your project, look up Test project to lea ...@@ -25,18 +27,17 @@ Refer the HTTP Proxy Server library in your project, look up Test project to lea
Install by nuget: Install by nuget:
Install-Package Titanium.Web.Proxy Install-Package Titanium.Web.Proxy -Pre
After installing nuget package mark following files to be copied to app directory
* makecert.exe
Setup HTTP proxy: Setup HTTP proxy:
```csharp ```csharp
var proxyServer = new ProxyServer(); var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true;
proxyServer.BeforeRequest += OnRequest; proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse; proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation; proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
...@@ -176,7 +177,9 @@ Sample request and response event handlers ...@@ -176,7 +177,9 @@ Sample request and response event handlers
``` ```
Future roadmap Future roadmap
============ ============
* 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
* Support updstream AutoProxy detection
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
...@@ -20,7 +20,8 @@ namespace Titanium.Web.Proxy.UnitTests ...@@ -20,7 +20,8 @@ namespace Titanium.Web.Proxy.UnitTests
{ {
var tasks = new List<Task>(); var tasks = new List<Task>();
var mgr = new CertificateManager("Titanium", "Titanium Root Certificate Authority"); var mgr = new CertificateManager("Titanium", "Titanium Root Certificate Authority",
new Lazy<Action<Exception>>(() => (e => { })).Value);
mgr.ClearIdleCertificates(1); mgr.ClearIdleCertificates(1);
......
using System;
using System.Net;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.UnitTests
{
[TestClass]
public class ProxyServerTests
{
[TestMethod]
public void GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenExceptionIsThrown()
{
// Arrange
var proxy = new ProxyServer();
const int port = 9999;
var firstIpAddress = IPAddress.Parse("127.0.0.1");
var secondIpAddress = IPAddress.Parse("127.0.0.1");
proxy.AddEndPoint(new ExplicitProxyEndPoint(firstIpAddress, port, false));
// Act
try
{
proxy.AddEndPoint(new ExplicitProxyEndPoint(secondIpAddress, port, false));
}
catch (Exception exc)
{
// Assert
StringAssert.Contains(exc.Message, "Cannot add another endpoint to same port");
return;
}
Assert.Fail("An exception should be thrown by now");
}
[TestMethod]
public void GivenOneEndpointIsAlreadyAddedToAddress_WhenAddingNewEndpointToExistingAddress_ThenTwoEndpointsExists()
{
// Arrange
var proxy = new ProxyServer();
const int port = 9999;
var firstIpAddress = IPAddress.Parse("127.0.0.1");
var secondIpAddress = IPAddress.Parse("192.168.1.1");
proxy.AddEndPoint(new ExplicitProxyEndPoint(firstIpAddress, port, false));
// Act
proxy.AddEndPoint(new ExplicitProxyEndPoint(secondIpAddress, port, false));
// Assert
Assert.AreEqual(2, proxy.ProxyEndPoints.Count);
}
[TestMethod]
public void GivenOneEndpointIsAlreadyAddedToPort_WhenAddingNewEndpointToExistingPort_ThenExceptionIsThrown()
{
// Arrange
var proxy = new ProxyServer();
const int port = 9999;
proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false));
// Act
try
{
proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false));
}
catch (Exception exc)
{
// Assert
StringAssert.Contains(exc.Message, "Cannot add another endpoint to same port");
return;
}
Assert.Fail("An exception should be thrown by now");
}
[TestMethod]
public void GivenOneEndpointIsAlreadyAddedToZeroPort_WhenAddingNewEndpointToExistingPort_ThenTwoEndpointsExists()
{
// Arrange
var proxy = new ProxyServer();
const int port = 0;
proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false));
// Act
proxy.AddEndPoint(new ExplicitProxyEndPoint(IPAddress.Loopback, port, false));
// Assert
Assert.AreEqual(2, proxy.ProxyEndPoints.Count);
}
}
}
...@@ -52,6 +52,7 @@ ...@@ -52,6 +52,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="CertificateManagerTests.cs" /> <Compile Include="CertificateManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyServerTests.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj"> <ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
......
...@@ -64,8 +64,6 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -64,8 +64,6 @@ namespace Titanium.Web.Proxy.EventArguments
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; } public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary> /// <summary>
/// Constructor to initialize the proxy /// Constructor to initialize the proxy
/// </summary> /// </summary>
......
...@@ -38,7 +38,8 @@ namespace Titanium.Web.Proxy.Http ...@@ -38,7 +38,8 @@ namespace Titanium.Web.Proxy.Http
{ {
if (processId == 0) if (processId == 0)
{ {
TcpRow tcpRow = TcpHelper.GetExtendedTcpTable().TcpRows.FirstOrDefault(row => row.LocalEndPoint.Port == ServerConnection.port); TcpRow tcpRow = TcpHelper.GetExtendedTcpTable().TcpRows
.FirstOrDefault(row => row.LocalEndPoint.Port == ServerConnection.port);
processId = tcpRow?.ProcessId ?? -1; processId = tcpRow?.ProcessId ?? -1;
} }
......
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
...@@ -10,7 +6,7 @@ using System.Threading; ...@@ -10,7 +6,7 @@ using System.Threading;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
{ {
public class CertEnrollEngine public class CertificateMaker
{ {
private Type typeX500DN; private Type typeX500DN;
...@@ -44,7 +40,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -44,7 +40,7 @@ namespace Titanium.Web.Proxy.Network
private object _SharedPrivateKey; private object _SharedPrivateKey;
public CertEnrollEngine() public CertificateMaker()
{ {
this.typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true); this.typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
this.typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true); this.typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true);
...@@ -62,12 +58,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -62,12 +58,12 @@ namespace Titanium.Web.Proxy.Network
this.typeAlternativeNamesExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionAlternativeNames"); this.typeAlternativeNamesExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionAlternativeNames");
} }
public X509Certificate2 CreateCert(string sSubjectCN, bool isRoot,X509Certificate2 signingCert=null) public X509Certificate2 MakeCertificate(string sSubjectCN, bool isRoot,X509Certificate2 signingCert=null)
{ {
return this.InternalCreateCert(sSubjectCN, isRoot, true, signingCert); return this.MakeCertificateInternal(sSubjectCN, isRoot, true, signingCert);
} }
private X509Certificate2 GenerateCertificate(bool IsRoot, string SubjectCN, string FullSubject, int PrivateKeyLength, string HashAlg, DateTime ValidFrom, DateTime ValidTo, X509Certificate2 SigningCertificate) private X509Certificate2 MakeCertificate(bool IsRoot, string SubjectCN, string FullSubject, int PrivateKeyLength, string HashAlg, DateTime ValidFrom, DateTime ValidTo, X509Certificate2 SigningCertificate)
{ {
X509Certificate2 cert; X509Certificate2 cert;
if (IsRoot != (null == SigningCertificate)) if (IsRoot != (null == SigningCertificate))
...@@ -193,7 +189,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -193,7 +189,7 @@ namespace Titanium.Web.Proxy.Network
return cert; return cert;
} }
private X509Certificate2 InternalCreateCert(string sSubjectCN, bool isRoot, bool switchToMTAIfNeeded,X509Certificate2 signingCert=null) private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot, bool switchToMTAIfNeeded,X509Certificate2 signingCert=null)
{ {
X509Certificate2 rCert=null; X509Certificate2 rCert=null;
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA) if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
...@@ -201,7 +197,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -201,7 +197,7 @@ namespace Titanium.Web.Proxy.Network
ManualResetEvent manualResetEvent = new ManualResetEvent(false); ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((object o) => ThreadPool.QueueUserWorkItem((object o) =>
{ {
rCert = this.InternalCreateCert(sSubjectCN, isRoot, false,signingCert); rCert = this.MakeCertificateInternal(sSubjectCN, isRoot, false,signingCert);
manualResetEvent.Set(); manualResetEvent.Set();
}); });
manualResetEvent.WaitOne(); manualResetEvent.WaitOne();
...@@ -220,11 +216,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -220,11 +216,11 @@ namespace Titanium.Web.Proxy.Network
{ {
if (!isRoot) if (!isRoot)
{ {
rCert = this.GenerateCertificate(false, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), signingCert); rCert = this.MakeCertificate(false, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), signingCert);
} }
else else
{ {
rCert = this.GenerateCertificate(true, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), null); rCert = this.MakeCertificate(true, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), null);
} }
} }
catch (Exception e) catch (Exception e)
......
...@@ -13,31 +13,34 @@ namespace Titanium.Web.Proxy.Network ...@@ -13,31 +13,34 @@ namespace Titanium.Web.Proxy.Network
/// </summary> /// </summary>
internal class CertificateManager : IDisposable internal class CertificateManager : IDisposable
{ {
private CertEnrollEngine certEngine = null; private CertificateMaker certEngine = null;
private bool clearCertificates { get; set; }
/// <summary> /// <summary>
/// Cache dictionary /// Cache dictionary
/// </summary> /// </summary>
private readonly IDictionary<string, CachedCertificate> certificateCache; private readonly IDictionary<string, CachedCertificate> certificateCache;
private Action<Exception> exceptionFunc;
internal string Issuer { get; private set; } internal string Issuer { get; private set; }
internal string RootCertificateName { get; private set; } internal string RootCertificateName { get; private set; }
internal X509Certificate2 rootCertificate { get; set; } internal X509Certificate2 rootCertificate { get; set; }
internal CertificateManager(string issuer, string rootCertificateName) internal CertificateManager(string issuer, string rootCertificateName, Action<Exception> exceptionFunc)
{ {
certEngine = new CertEnrollEngine(); this.exceptionFunc = exceptionFunc;
certEngine = new CertificateMaker();
Issuer = issuer; Issuer = issuer;
RootCertificateName = rootCertificateName; RootCertificateName = rootCertificateName;
certificateCache = new ConcurrentDictionary<string, CachedCertificate>(); certificateCache = new ConcurrentDictionary<string, CachedCertificate>();
} }
X509Certificate2 GetRootCertificate() internal X509Certificate2 GetRootCertificate()
{ {
var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "rootCert.pfx"); var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "rootCert.pfx");
...@@ -50,7 +53,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -50,7 +53,7 @@ namespace Titanium.Web.Proxy.Network
} }
catch (Exception e) catch (Exception e)
{ {
ProxyServer.ExceptionFunc(e); exceptionFunc(e);
return null; return null;
} }
} }
...@@ -74,7 +77,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -74,7 +77,7 @@ namespace Titanium.Web.Proxy.Network
} }
catch(Exception e) catch(Exception e)
{ {
ProxyServer.ExceptionFunc(e); exceptionFunc(e);
} }
if (rootCertificate != null) if (rootCertificate != null)
{ {
...@@ -85,7 +88,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -85,7 +88,7 @@ namespace Titanium.Web.Proxy.Network
} }
catch(Exception e) catch(Exception e)
{ {
ProxyServer.ExceptionFunc(e); exceptionFunc(e);
} }
} }
return rootCertificate != null; return rootCertificate != null;
...@@ -97,7 +100,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -97,7 +100,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="certificateName"></param> /// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param> /// <param name="isRootCertificate"></param>
/// <returns></returns> /// <returns></returns>
public virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate) internal virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{ {
try try
{ {
...@@ -119,11 +122,11 @@ namespace Titanium.Web.Proxy.Network ...@@ -119,11 +122,11 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
certificate = certEngine.CreateCert(certificateName, isRootCertificate, rootCertificate); certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, rootCertificate);
} }
catch(Exception e) catch(Exception e)
{ {
ProxyServer.ExceptionFunc(e); exceptionFunc(e);
} }
if (certificate != null && !certificateCache.ContainsKey(certificateName)) if (certificate != null && !certificateCache.ContainsKey(certificateName))
{ {
...@@ -147,9 +150,6 @@ namespace Titanium.Web.Proxy.Network ...@@ -147,9 +150,6 @@ namespace Titanium.Web.Proxy.Network
} }
private bool clearCertificates { get; set; }
/// <summary> /// <summary>
/// Stops the certificate cache clear process /// Stops the certificate cache clear process
/// </summary> /// </summary>
...@@ -187,7 +187,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -187,7 +187,7 @@ namespace Titanium.Web.Proxy.Network
} }
} }
public bool TrustRootCertificate() internal bool TrustRootCertificate()
{ {
if (rootCertificate == null) if (rootCertificate == null)
{ {
...@@ -195,19 +195,25 @@ namespace Titanium.Web.Proxy.Network ...@@ -195,19 +195,25 @@ namespace Titanium.Web.Proxy.Network
} }
try try
{ {
X509Store x509Store = new X509Store(StoreName.Root, StoreLocation.CurrentUser); X509Store x509RootStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
x509Store.Open(OpenFlags.ReadWrite); var x509PersonalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
try try
{ {
x509Store.Add(rootCertificate); x509RootStore.Add(rootCertificate);
x509PersonalStore.Add(rootCertificate);
} }
finally finally
{ {
x509Store.Close(); x509RootStore.Close();
x509PersonalStore.Close();
} }
return true; return true;
} }
catch (Exception exception) catch
{ {
return false; return false;
} }
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy
{
public partial class ProxyServer
{
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> Headers)
{
if (AuthenticateUserFunc == null)
{
return true;
}
try
{
if (!Headers.Where(t => t.Name == "Proxy-Authorization").Any())
{
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Required", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
else
{
var headerValue = Headers.Where(t => t.Name == "Proxy-Authorization").FirstOrDefault().Value.Trim();
if (!headerValue.ToLower().StartsWith("basic"))
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
headerValue = headerValue.Substring(5).Trim();
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
if (decoded.Contains(":") == false)
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
var username = decoded.Substring(0, decoded.IndexOf(':'));
var password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password).ConfigureAwait(false);
}
}
catch (Exception e)
{
ExceptionFunc(e);
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
}
}
}
...@@ -17,37 +17,16 @@ namespace Titanium.Web.Proxy ...@@ -17,37 +17,16 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public partial class ProxyServer : IDisposable public partial class ProxyServer : IDisposable
{ {
private static readonly Lazy<Action<Exception>> _defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { }));
private static Action<Exception> _exceptionFunc; /// <summary>
public static Action<Exception> ExceptionFunc /// Is the root certificate used by this proxy is valid?
{ /// </summary>
get private bool certValidated { get; set; }
{
return _exceptionFunc ?? _defaultExceptionFunc.Value;
}
set
{
_exceptionFunc = value;
}
}
public Func<string, string, Task<bool>> AuthenticateUserFunc
{
get;
set;
}
//parameter is list of headers
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc
{
get;
set;
}
//parameter is list of headers
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc
{
get;
set;
}
/// <summary>
/// Is the proxy currently running
/// </summary>
private bool proxyRunning { get; set; }
/// <summary> /// <summary>
/// Manages certificates used by this proxy /// Manages certificates used by this proxy
...@@ -55,26 +34,26 @@ namespace Titanium.Web.Proxy ...@@ -55,26 +34,26 @@ namespace Titanium.Web.Proxy
private CertificateManager certificateCacheManager { get; set; } private CertificateManager certificateCacheManager { get; set; }
/// <summary> /// <summary>
/// A object that creates tcp connection to server /// An default exception log func
/// </summary> /// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; set; } private readonly Lazy<Action<Exception>> defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { }));
/// <summary> /// <summary>
/// Manage system proxy settings /// backing exception func for exposed public property
/// </summary> /// </summary>
private SystemProxyManager systemProxySettingsManager { get; set; } private Action<Exception> exceptionFunc;
private FireFoxProxySettingsManager firefoxProxySettingsManager { get; set; }
/// <summary> /// <summary>
/// Does the root certificate used by this proxy is trusted by the machine? /// A object that creates tcp connection to server
/// </summary> /// </summary>
private bool certTrusted { get; set; } private TcpConnectionFactory tcpConnectionFactory { get; set; }
/// <summary> /// <summary>
/// Is the proxy currently running /// Manage system proxy settings
/// </summary> /// </summary>
private bool proxyRunning { get; set; } private SystemProxyManager systemProxySettingsManager { get; set; }
private FireFoxProxySettingsManager firefoxProxySettingsManager { get; set; }
/// <summary> /// <summary>
/// Buffer size used throughout this proxy /// Buffer size used throughout this proxy
...@@ -88,9 +67,19 @@ namespace Titanium.Web.Proxy ...@@ -88,9 +67,19 @@ namespace Titanium.Web.Proxy
/// <summary> /// <summary>
/// Name of the root certificate /// Name of the root certificate
/// If no certificate is provided then a default Root Certificate will be created and used
/// The provided root certificate has to be in the proxy exe directory with the private key
/// The root certificate file should be named as "rootCert.pfx"
/// </summary> /// </summary>
public string RootCertificateName { get; set; } public string RootCertificateName { get; set; }
/// <summary>
/// Trust the RootCertificate used by this proxy server
/// Note that this do not make the client trust the certificate!
/// This would import the root certificate to the certificate store of machine that runs this proxy server
/// </summary>
public bool TrustRootCertificate { get; set; }
/// <summary> /// <summary>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly? /// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled /// Broken 100 contunue implementations on server/client may cause problems if enabled
...@@ -137,6 +126,51 @@ namespace Titanium.Web.Proxy ...@@ -137,6 +126,51 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback; public event Func<object, CertificateSelectionEventArgs, Task> ClientCertificateSelectionCallback;
/// <summary>
/// Callback for error events in proxy
/// </summary>
public Action<Exception> ExceptionFunc
{
get
{
return exceptionFunc ?? defaultExceptionFunc.Value;
}
set
{
exceptionFunc = value;
}
}
/// <summary>
/// A callback to authenticate clients
/// Parameters are username, password provided by client
/// return true for successful authentication
/// </summary>
public Func<string, string, Task<bool>> AuthenticateUserFunc
{
get;
set;
}
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTP requests
/// return the ExternalProxy object with valid credentials
/// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpProxyFunc
{
get;
set;
}
/// <summary>
/// A callback to provide authentication credentials for up stream proxy this proxy is using for HTTPS requests
/// return the ExternalProxy object with valid credentials
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc
{
get;
set;
}
/// <summary> /// <summary>
/// A list of IpAddress & port this proxy is listening to /// A list of IpAddress & port this proxy is listening to
/// </summary> /// </summary>
...@@ -180,7 +214,7 @@ namespace Titanium.Web.Proxy ...@@ -180,7 +214,7 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param> /// <param name="endPoint"></param>
public void AddEndPoint(ProxyEndPoint endPoint) public void AddEndPoint(ProxyEndPoint endPoint)
{ {
if (ProxyEndPoints.Any(x => x.IpAddress == endPoint.IpAddress && x.Port == endPoint.Port)) if (ProxyEndPoints.Any(x => x.IpAddress.Equals(endPoint.IpAddress) && endPoint.Port != 0 && x.Port == endPoint.Port))
{ {
throw new Exception("Cannot add another endpoint to same port & ip address"); throw new Exception("Cannot add another endpoint to same port & ip address");
} }
...@@ -254,13 +288,14 @@ namespace Titanium.Web.Proxy ...@@ -254,13 +288,14 @@ namespace Titanium.Web.Proxy
//If certificate was trusted by the machine //If certificate was trusted by the machine
if (certTrusted) if (certValidated)
{ {
systemProxySettingsManager.SetHttpsProxy( systemProxySettingsManager.SetHttpsProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(),
endPoint.Port); endPoint.Port);
} }
endPoint.IsSystemHttpsProxy = true; endPoint.IsSystemHttpsProxy = true;
#if !DEBUG #if !DEBUG
...@@ -304,9 +339,14 @@ namespace Titanium.Web.Proxy ...@@ -304,9 +339,14 @@ namespace Titanium.Web.Proxy
} }
certificateCacheManager = new CertificateManager(RootCertificateIssuerName, certificateCacheManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName); RootCertificateName, ExceptionFunc);
certTrusted = certificateCacheManager.CreateTrustedRootCertificate(); certValidated = certificateCacheManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
certificateCacheManager.TrustRootCertificate();
}
foreach (var endPoint in ProxyEndPoints) foreach (var endPoint in ProxyEndPoints)
{ {
...@@ -442,6 +482,12 @@ namespace Titanium.Web.Proxy ...@@ -442,6 +482,12 @@ namespace Titanium.Web.Proxy
{ {
if (tcpClient != null) if (tcpClient != null)
{ {
//This line is important!
//contributors please don't remove it without discussion
//It helps to avoid eventual deterioration of performance due to TCP port exhaustion
//due to default TCP CLOSE_WAIT timeout for 4 minutes
tcpClient.LingerState = new LingerOption(true, 0);
tcpClient.Client.Shutdown(SocketShutdown.Both); tcpClient.Client.Shutdown(SocketShutdown.Both);
tcpClient.Client.Close(); tcpClient.Client.Close();
tcpClient.Client.Dispose(); tcpClient.Client.Dispose();
......
...@@ -25,83 +25,6 @@ namespace Titanium.Web.Proxy ...@@ -25,83 +25,6 @@ namespace Titanium.Web.Proxy
partial class ProxyServer partial class ProxyServer
{ {
private async Task<bool> CheckAuthorization(StreamWriter clientStreamWriter, IEnumerable<HttpHeader> Headers)
{
if (AuthenticateUserFunc == null)
{
return true;
}
try
{
if (!Headers.Where(t => t.Name == "Proxy-Authorization").Any())
{
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Required", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
else
{
var headerValue = Headers.Where(t => t.Name == "Proxy-Authorization").FirstOrDefault().Value.Trim();
if (!headerValue.ToLower().StartsWith("basic"))
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
headerValue = headerValue.Substring(5).Trim();
var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue));
if (decoded.Contains(":") == false)
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
var username = decoded.Substring(0, decoded.IndexOf(':'));
var password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password).ConfigureAwait(false);
}
}
catch (Exception e)
{
//Return not authorized
await WriteResponseStatus(new Version(1, 1), "407",
"Proxy Authentication Invalid", clientStreamWriter);
var response = new Response();
response.ResponseHeaders = new Dictionary<string, HttpHeader>();
response.ResponseHeaders.Add("Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\""));
response.ResponseHeaders.Add("Proxy-Connection", new HttpHeader("Proxy-Connection", "close"));
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
}
//This is called when client is aware of proxy //This is called when client is aware of proxy
//So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy //So for HTTPS requests client would send CONNECT header to negotiate a secure tcp tunnel via proxy
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client) private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
...@@ -172,6 +95,7 @@ namespace Titanium.Web.Proxy ...@@ -172,6 +95,7 @@ namespace Titanium.Web.Proxy
var newHeader = new HttpHeader(header[0], header[1]); var newHeader = new HttpHeader(header[0], header[1]);
connectRequestHeaders.Add(newHeader); connectRequestHeaders.Add(newHeader);
} }
if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false) if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false)
{ {
Dispose(clientStream, clientStreamReader, clientStreamWriter, null); Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
...@@ -287,6 +211,7 @@ namespace Titanium.Web.Proxy ...@@ -287,6 +211,7 @@ namespace Titanium.Web.Proxy
else else
{ {
clientStreamReader = new CustomBinaryReader(clientStream); clientStreamReader = new CustomBinaryReader(clientStream);
clientStreamWriter = new StreamWriter(clientStream);
} }
//now read the request line //now read the request line
...@@ -413,7 +338,7 @@ namespace Titanium.Web.Proxy ...@@ -413,7 +338,7 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
ProxyServer.ExceptionFunc(e); ExceptionFunc(e);
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args); Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return; return;
} }
...@@ -525,7 +450,6 @@ namespace Titanium.Web.Proxy ...@@ -525,7 +450,6 @@ namespace Titanium.Web.Proxy
} }
PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession); PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
...@@ -577,7 +501,7 @@ namespace Titanium.Web.Proxy ...@@ -577,7 +501,7 @@ namespace Titanium.Web.Proxy
} }
catch (Exception e) catch (Exception e)
{ {
ProxyServer.ExceptionFunc(e); ExceptionFunc(e);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args); Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break; break;
} }
......
...@@ -30,6 +30,7 @@ namespace Titanium.Web.Proxy ...@@ -30,6 +30,7 @@ namespace Titanium.Web.Proxy
} }
args.ReRequest = false; args.ReRequest = false;
//If user requested call back then do it //If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{ {
...@@ -43,11 +44,13 @@ namespace Titanium.Web.Proxy ...@@ -43,11 +44,13 @@ namespace Titanium.Web.Proxy
await Task.WhenAll(handlerTasks); await Task.WhenAll(handlerTasks);
} }
if(args.ReRequest) if(args.ReRequest)
{ {
await HandleHttpSessionRequestInternal(null, args, null, null, true).ConfigureAwait(false); await HandleHttpSessionRequestInternal(null, args, null, null, true).ConfigureAwait(false);
return; return;
} }
args.WebSession.Response.ResponseLocked = true; args.WebSession.Response.ResponseLocked = true;
//Write back to client 100-conitinue response if that's what server returned //Write back to client 100-conitinue response if that's what server returned
...@@ -116,8 +119,9 @@ namespace Titanium.Web.Proxy ...@@ -116,8 +119,9 @@ namespace Titanium.Web.Proxy
await args.ProxyClient.ClientStream.FlushAsync(); await args.ProxyClient.ClientStream.FlushAsync();
} }
catch catch(Exception e)
{ {
ExceptionFunc(e);
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args); args.ProxyClient.ClientStreamWriter, args);
} }
......
...@@ -65,7 +65,7 @@ ...@@ -65,7 +65,7 @@
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" /> <Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" /> <Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Network\CachedCertificate.cs" /> <Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\CertEnrollEngine.cs" /> <Compile Include="Network\CertificateMaker.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" />
...@@ -83,6 +83,7 @@ ...@@ -83,6 +83,7 @@
<Compile Include="Models\HttpHeader.cs" /> <Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyAuthorizationHandler.cs" />
<Compile Include="RequestHandler.cs" /> <Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" /> <Compile Include="ResponseHandler.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" /> <Compile Include="Helpers\CustomBinaryReader.cs" />
......
...@@ -19,6 +19,5 @@ ...@@ -19,6 +19,5 @@
</metadata> </metadata>
<files> <files>
<file src="bin\$configuration$\Titanium.Web.Proxy.dll" target="lib\net45" /> <file src="bin\$configuration$\Titanium.Web.Proxy.dll" target="lib\net45" />
<file src="bin\$configuration$\makecert.exe" target="content" />
</files> </files>
</package> </package>
\ No newline at end of file
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