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
public ProxyTestController()
{
proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
}
public void StartProxy()
......
......@@ -17,6 +17,8 @@ Features
* Safely relays WebSocket requests over Http
* Support mutual SSL authentication
* Fully asynchronous proxy
* Supports proxy authentication
Usage
=====
......@@ -25,18 +27,17 @@ Refer the HTTP Proxy Server library in your project, look up Test project to lea
Install by nuget:
Install-Package Titanium.Web.Proxy
After installing nuget package mark following files to be copied to app directory
* makecert.exe
Install-Package Titanium.Web.Proxy -Pre
Setup HTTP proxy:
```csharp
var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true;
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
......@@ -176,7 +177,9 @@ Sample request and response event handlers
```
Future roadmap
============
* Implement Kerberos/NTLM authentication over HTTP protocols for windows domain
* Support Server Name Indication (SNI) for transparent endpoints
* 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
{
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);
......
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 @@
<ItemGroup>
<Compile Include="CertificateManagerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyServerTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
......
......@@ -64,8 +64,6 @@ namespace Titanium.Web.Proxy.EventArguments
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary>
/// Constructor to initialize the proxy
/// </summary>
......
......@@ -38,7 +38,8 @@ namespace Titanium.Web.Proxy.Http
{
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;
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
......@@ -10,7 +6,7 @@ using System.Threading;
namespace Titanium.Web.Proxy.Network
{
public class CertEnrollEngine
public class CertificateMaker
{
private Type typeX500DN;
......@@ -44,7 +40,7 @@ namespace Titanium.Web.Proxy.Network
private object _SharedPrivateKey;
public CertEnrollEngine()
public CertificateMaker()
{
this.typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
this.typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true);
......@@ -62,12 +58,12 @@ namespace Titanium.Web.Proxy.Network
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;
if (IsRoot != (null == SigningCertificate))
......@@ -193,7 +189,7 @@ namespace Titanium.Web.Proxy.Network
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;
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
......@@ -201,7 +197,7 @@ namespace Titanium.Web.Proxy.Network
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((object o) =>
{
rCert = this.InternalCreateCert(sSubjectCN, isRoot, false,signingCert);
rCert = this.MakeCertificateInternal(sSubjectCN, isRoot, false,signingCert);
manualResetEvent.Set();
});
manualResetEvent.WaitOne();
......@@ -220,11 +216,11 @@ namespace Titanium.Web.Proxy.Network
{
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
{
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)
......
......@@ -13,31 +13,34 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
internal class CertificateManager : IDisposable
{
private CertEnrollEngine certEngine = null;
private CertificateMaker certEngine = null;
private bool clearCertificates { get; set; }
/// <summary>
/// Cache dictionary
/// </summary>
private readonly IDictionary<string, CachedCertificate> certificateCache;
private Action<Exception> exceptionFunc;
internal string Issuer { get; private set; }
internal string RootCertificateName { get; private 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;
RootCertificateName = rootCertificateName;
RootCertificateName = rootCertificateName;
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");
......@@ -50,7 +53,7 @@ namespace Titanium.Web.Proxy.Network
}
catch (Exception e)
{
ProxyServer.ExceptionFunc(e);
exceptionFunc(e);
return null;
}
}
......@@ -74,7 +77,7 @@ namespace Titanium.Web.Proxy.Network
}
catch(Exception e)
{
ProxyServer.ExceptionFunc(e);
exceptionFunc(e);
}
if (rootCertificate != null)
{
......@@ -85,7 +88,7 @@ namespace Titanium.Web.Proxy.Network
}
catch(Exception e)
{
ProxyServer.ExceptionFunc(e);
exceptionFunc(e);
}
}
return rootCertificate != null;
......@@ -97,7 +100,7 @@ namespace Titanium.Web.Proxy.Network
/// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param>
/// <returns></returns>
public virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
internal virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{
try
{
......@@ -119,11 +122,11 @@ namespace Titanium.Web.Proxy.Network
{
try
{
certificate = certEngine.CreateCert(certificateName, isRootCertificate, rootCertificate);
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, rootCertificate);
}
catch(Exception e)
{
ProxyServer.ExceptionFunc(e);
exceptionFunc(e);
}
if (certificate != null && !certificateCache.ContainsKey(certificateName))
{
......@@ -147,9 +150,6 @@ namespace Titanium.Web.Proxy.Network
}
private bool clearCertificates { get; set; }
/// <summary>
/// Stops the certificate cache clear process
/// </summary>
......@@ -187,7 +187,7 @@ namespace Titanium.Web.Proxy.Network
}
}
public bool TrustRootCertificate()
internal bool TrustRootCertificate()
{
if (rootCertificate == null)
{
......@@ -195,19 +195,25 @@ namespace Titanium.Web.Proxy.Network
}
try
{
X509Store x509Store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
x509Store.Open(OpenFlags.ReadWrite);
X509Store x509RootStore = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
var x509PersonalStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
x509RootStore.Open(OpenFlags.ReadWrite);
x509PersonalStore.Open(OpenFlags.ReadWrite);
try
{
x509Store.Add(rootCertificate);
x509RootStore.Add(rootCertificate);
x509PersonalStore.Add(rootCertificate);
}
finally
{
x509Store.Close();
x509RootStore.Close();
x509PersonalStore.Close();
}
return true;
}
catch (Exception exception)
catch
{
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
/// </summary>
public partial class ProxyServer : IDisposable
{
private static readonly Lazy<Action<Exception>> _defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { }));
private static Action<Exception> _exceptionFunc;
public static Action<Exception> ExceptionFunc
{
get
{
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 root certificate used by this proxy is valid?
/// </summary>
private bool certValidated { get; set; }
/// <summary>
/// Is the proxy currently running
/// </summary>
private bool proxyRunning { get; set; }
/// <summary>
/// Manages certificates used by this proxy
......@@ -55,26 +34,26 @@ namespace Titanium.Web.Proxy
private CertificateManager certificateCacheManager { get; set; }
/// <summary>
/// A object that creates tcp connection to server
/// An default exception log func
/// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; set; }
private readonly Lazy<Action<Exception>> defaultExceptionFunc = new Lazy<Action<Exception>>(() => (e => { }));
/// <summary>
/// Manage system proxy settings
/// backing exception func for exposed public property
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; set; }
private FireFoxProxySettingsManager firefoxProxySettingsManager { get; set; }
private Action<Exception> exceptionFunc;
/// <summary>
/// Does the root certificate used by this proxy is trusted by the machine?
/// A object that creates tcp connection to server
/// </summary>
private bool certTrusted { get; set; }
private TcpConnectionFactory tcpConnectionFactory { get; set; }
/// <summary>
/// Is the proxy currently running
/// Manage system proxy settings
/// </summary>
private bool proxyRunning { get; set; }
private SystemProxyManager systemProxySettingsManager { get; set; }
private FireFoxProxySettingsManager firefoxProxySettingsManager { get; set; }
/// <summary>
/// Buffer size used throughout this proxy
......@@ -88,9 +67,19 @@ namespace Titanium.Web.Proxy
/// <summary>
/// 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>
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>
/// Does this proxy uses the HTTP protocol 100 continue behaviour strictly?
/// Broken 100 contunue implementations on server/client may cause problems if enabled
......@@ -137,6 +126,51 @@ namespace Titanium.Web.Proxy
/// </summary>
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>
/// A list of IpAddress & port this proxy is listening to
/// </summary>
......@@ -180,7 +214,7 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param>
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");
}
......@@ -254,13 +288,14 @@ namespace Titanium.Web.Proxy
//If certificate was trusted by the machine
if (certTrusted)
if (certValidated)
{
systemProxySettingsManager.SetHttpsProxy(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(),
endPoint.Port);
}
endPoint.IsSystemHttpsProxy = true;
#if !DEBUG
......@@ -304,9 +339,14 @@ namespace Titanium.Web.Proxy
}
certificateCacheManager = new CertificateManager(RootCertificateIssuerName,
RootCertificateName);
RootCertificateName, ExceptionFunc);
certTrusted = certificateCacheManager.CreateTrustedRootCertificate();
certValidated = certificateCacheManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
certificateCacheManager.TrustRootCertificate();
}
foreach (var endPoint in ProxyEndPoints)
{
......@@ -442,6 +482,12 @@ namespace Titanium.Web.Proxy
{
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.Close();
tcpClient.Client.Dispose();
......
......@@ -25,83 +25,6 @@ namespace Titanium.Web.Proxy
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
//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)
......@@ -172,6 +95,7 @@ namespace Titanium.Web.Proxy
var newHeader = new HttpHeader(header[0], header[1]);
connectRequestHeaders.Add(newHeader);
}
if (await CheckAuthorization(clientStreamWriter, connectRequestHeaders) == false)
{
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
......@@ -287,6 +211,7 @@ namespace Titanium.Web.Proxy
else
{
clientStreamReader = new CustomBinaryReader(clientStream);
clientStreamWriter = new StreamWriter(clientStream);
}
//now read the request line
......@@ -413,7 +338,7 @@ namespace Titanium.Web.Proxy
}
catch (Exception e)
{
ProxyServer.ExceptionFunc(e);
ExceptionFunc(e);
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args);
return;
}
......@@ -525,7 +450,6 @@ namespace Titanium.Web.Proxy
}
PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;
......@@ -577,7 +501,7 @@ namespace Titanium.Web.Proxy
}
catch (Exception e)
{
ProxyServer.ExceptionFunc(e);
ExceptionFunc(e);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
break;
}
......
......@@ -30,6 +30,7 @@ namespace Titanium.Web.Proxy
}
args.ReRequest = false;
//If user requested call back then do it
if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked)
{
......@@ -43,11 +44,13 @@ namespace Titanium.Web.Proxy
await Task.WhenAll(handlerTasks);
}
if(args.ReRequest)
{
await HandleHttpSessionRequestInternal(null, args, null, null, true).ConfigureAwait(false);
return;
}
args.WebSession.Response.ResponseLocked = true;
//Write back to client 100-conitinue response if that's what server returned
......@@ -116,8 +119,9 @@ namespace Titanium.Web.Proxy
await args.ProxyClient.ClientStream.FlushAsync();
}
catch
catch(Exception e)
{
ExceptionFunc(e);
Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader,
args.ProxyClient.ClientStreamWriter, args);
}
......
......@@ -65,7 +65,7 @@
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\CertEnrollEngine.cs" />
<Compile Include="Network\CertificateMaker.cs" />
<Compile Include="Network\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Extensions\HttpWebResponseExtensions.cs" />
......@@ -83,6 +83,7 @@
<Compile Include="Models\HttpHeader.cs" />
<Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ProxyAuthorizationHandler.cs" />
<Compile Include="RequestHandler.cs" />
<Compile Include="ResponseHandler.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" />
......
......@@ -19,6 +19,5 @@
</metadata>
<files>
<file src="bin\$configuration$\Titanium.Web.Proxy.dll" target="lib\net45" />
<file src="bin\$configuration$\makecert.exe" target="content" />
</files>
</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