Commit bb6691d8 authored by titanium007's avatar titanium007

Merge pull request #40 from titanium007/bug-fixes

Bug fixes
parents b8c85c74 6800b598
......@@ -21,7 +21,7 @@ if(!$Configuration) { $Configuration = $env:Configuration }
if(!$Configuration) { $Configuration = "Release" }
if(!$Version) { $Version = $env:APPVEYOR_BUILD_VERSION }
if(!$Version) { $Version = "1.0.$BuildNumber" }
if(!$Version) { $Version = "2.0.$BuildNumber" }
if(!$Branch) { $Branch = $env:APPVEYOR_REPO_BRANCH }
if(!$Branch) { $Branch = "local" }
......
......@@ -36,11 +36,22 @@ Setup HTTP proxy:
// listen to client request & server response events
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//for example dropbox.com
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Loopback, 8000, true){
ExcludedHostNameRegex = new List<string>() { "dropbox.com" }
};
ProxyServer.EnableSSL = true;
ProxyServer.SetAsSystemProxy = true;
ProxyServer.Start();
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Loopback, 8001, true);
ProxyServer.AddEndPoint(explicitEndPoint);
ProxyServer.AddEndPoint(transparentEndPoint);
ProxyServer.Start();
ProxyServer.SetAsSystemProxy(explicitEndPoint);
//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
......
......@@ -14,22 +14,6 @@ namespace Titanium.Web.Proxy.Test
NativeMethods.SetConsoleCtrlHandler(NativeMethods.Handler, true);
Console.Write("Do you want to monitor HTTPS? (Y/N):");
var readLine = Console.ReadLine();
if (readLine != null && readLine.Trim().ToLower() == "y")
{
Controller.EnableSsl = true;
}
Console.Write("Do you want to set this as a System Proxy? (Y/N):");
var line = Console.ReadLine();
if (line != null && line.Trim().ToLower() == "y")
{
Controller.SetAsSystemProxy = true;
}
//Start proxy controller
Controller.StartProxy();
......
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Test
{
public class ProxyTestController
{
public int ListeningPort { get; set; }
public bool EnableSsl { get; set; }
public bool SetAsSystemProxy { get; set; }
public void StartProxy()
{
ProxyServer.BeforeRequest += OnRequest;
ProxyServer.BeforeResponse += OnResponse;
ProxyServer.EnableSsl = EnableSsl;
ProxyServer.SetAsSystemProxy = SetAsSystemProxy;
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//for example dropbox.com
ProxyServer.ExcludedHttpsHostNameRegex.Add(".dropbox.com");
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Loopback, 8000, true){
ExcludedHostNameRegex = new List<string>() { "dropbox.com" }
};
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Loopback, 8001, true);
ProxyServer.AddEndPoint(explicitEndPoint);
ProxyServer.AddEndPoint(transparentEndPoint);
ProxyServer.Start();
ProxyServer.ListeningPort = ProxyServer.ListeningPort;
foreach (var endPoint in ProxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
ProxyServer.SetAsSystemProxy(explicitEndPoint);
Console.WriteLine("Proxy listening on local machine port: {0} ", ProxyServer.ListeningPort);
}
public void Stop()
......
......@@ -8,8 +8,14 @@ namespace Titanium.Web.Proxy.Extensions
{
public static Encoding GetResponseEncoding(this HttpWebSession response)
{
if (string.IsNullOrEmpty(response.Response.CharacterSet)) return Encoding.GetEncoding("ISO-8859-1");
return Encoding.GetEncoding(response.Response.CharacterSet.Replace(@"""", string.Empty));
if (string.IsNullOrEmpty(response.Response.CharacterSet))
return Encoding.GetEncoding("ISO-8859-1");
try
{
return Encoding.GetEncoding(response.Response.CharacterSet.Replace(@"""", string.Empty));
}
catch { return Encoding.GetEncoding("ISO-8859-1"); }
}
}
}
\ No newline at end of file
......@@ -8,13 +8,17 @@ namespace Titanium.Web.Proxy.Extensions
{
public static void CopyToAsync(this Stream input, string initialData, Stream output, int bufferSize)
{
var bytes = Encoding.ASCII.GetBytes(initialData);
output.Write(bytes, 0, bytes.Length);
if(!string.IsNullOrEmpty(initialData))
{
var bytes = Encoding.ASCII.GetBytes(initialData);
output.Write(bytes, 0, bytes.Length);
}
CopyToAsync(input, output, bufferSize);
}
//http://stackoverflow.com/questions/1540658/net-asynchronous-stream-read-write
public static void CopyToAsync(this Stream input, Stream output, int bufferSize)
private static void CopyToAsync(this Stream input, Stream output, int bufferSize)
{
try
{
......
......@@ -51,7 +51,7 @@ namespace Titanium.Web.Proxy.Helpers
try
{
sslStream = new SslStream(tunnelStream);
sslStream.AuthenticateAsClient(hostName);
sslStream.AuthenticateAsClient(hostName, null, ProxyServer.SupportedProtocols, false);
tunnelStream = sslStream;
}
catch
......@@ -69,10 +69,10 @@ namespace Titanium.Web.Proxy.Helpers
if (sb != null)
clientStream.CopyToAsync(sb.ToString(), tunnelStream, BUFFER_SIZE);
else
clientStream.CopyToAsync(tunnelStream, BUFFER_SIZE);
clientStream.CopyToAsync(string.Empty, tunnelStream, BUFFER_SIZE);
});
var receiveRelay = Task.Factory.StartNew(() => tunnelStream.CopyToAsync(clientStream, BUFFER_SIZE));
var receiveRelay = Task.Factory.StartNew(() => tunnelStream.CopyToAsync(string.Empty, clientStream, BUFFER_SIZE));
Task.WaitAll(sendRelay, receiveRelay);
}
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace Titanium.Web.Proxy.Models
{
public abstract class ProxyEndPoint
{
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
{
this.IpAddress = IpAddress;
this.Port = Port;
this.EnableSsl = EnableSsl;
}
public IPAddress IpAddress { get; internal set; }
public int Port { get; internal set; }
public bool EnableSsl { get; internal set; }
internal TcpListener listener { get; set; }
}
public class ExplicitProxyEndPoint : ProxyEndPoint
{
internal bool IsSystemProxy { get; set; }
public List<string> ExcludedHostNameRegex { get; set; }
public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
}
}
public class TransparentProxyEndPoint : ProxyEndPoint
{
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
}
}
}
......@@ -9,6 +9,7 @@ using System.IO;
using System.Net.Security;
using Titanium.Web.Proxy.Helpers;
using System.Threading;
using System.Security.Authentication;
namespace Titanium.Web.Proxy.Network
{
......@@ -76,7 +77,7 @@ namespace Titanium.Web.Proxy.Network
try
{
sslStream = new SslStream(stream);
sslStream.AuthenticateAsClient(Hostname);
sslStream.AuthenticateAsClient(Hostname, null, ProxyServer.SupportedProtocols , false);
stream = (Stream)sslStream;
}
catch
......
......@@ -9,7 +9,10 @@ using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using System.Linq;
using System.Security.Authentication;
namespace Titanium.Web.Proxy
{
......@@ -31,99 +34,133 @@ namespace Titanium.Web.Proxy
private static readonly byte[] ChunkEnd =
Encoding.ASCII.GetBytes(0.ToString("x2") + Environment.NewLine + Environment.NewLine);
private static TcpListener _listener;
public static List<string> ExcludedHttpsHostNameRegex = new List<string>();
#if NET45
internal static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3 ;
#else
public static SslProtocols SupportedProtocols = SslProtocols.Tls | SslProtocols.Ssl3;
#endif
static ProxyServer()
{
CertManager = new CertificateManager("Titanium",
"Titanium Root Certificate Authority");
ListeningIpAddress = IPAddress.Any;
ListeningPort = 0;
ProxyEndPoints = new List<ProxyEndPoint>();
Initialize();
}
private static CertificateManager CertManager { get; set; }
private static bool EnableSsl { get; set; }
private static bool certTrusted { get; set; }
private static bool proxyStarted { get; set; }
public static string RootCertificateName { get; set; }
public static bool EnableSsl { get; set; }
public static bool SetAsSystemProxy { get; set; }
public static int ListeningPort { get; set; }
public static IPAddress ListeningIpAddress { get; set; }
public static event EventHandler<SessionEventArgs> BeforeRequest;
public static event EventHandler<SessionEventArgs> BeforeResponse;
public static List<ProxyEndPoint> ProxyEndPoints { get; set; }
public static void Initialize()
{
Task.Factory.StartNew(()=>TcpConnectionManager.ClearIdleConnections());
{
Task.Factory.StartNew(() => TcpConnectionManager.ClearIdleConnections());
}
public static void AddEndPoint(ProxyEndPoint endPoint)
{
if (proxyStarted)
throw new Exception("Cannot add end points after proxy started.");
ProxyEndPoints.Add(endPoint);
}
public static bool Start()
public static void SetAsSystemProxy(ExplicitProxyEndPoint endPoint)
{
_listener = new TcpListener(ListeningIpAddress, ListeningPort);
_listener.Start();
if (ProxyEndPoints.Contains(endPoint) == false)
throw new Exception("Cannot set endPoints not added to proxy as system proxy");
ListeningPort = ((IPEndPoint)_listener.LocalEndpoint).Port;
// accept clients asynchronously
_listener.BeginAcceptTcpClient(OnAcceptConnection, _listener);
if (!proxyStarted)
throw new Exception("Cannot set system proxy settings before proxy has been started.");
var certTrusted = false;
//clear any settings previously added
ProxyEndPoints.OfType<ExplicitProxyEndPoint>().ToList().ForEach(x => x.IsSystemProxy = false);
if (EnableSsl)
certTrusted = CertManager.CreateTrustedRootCertificate();
endPoint.IsSystemProxy = true;
if (SetAsSystemProxy)
{
SystemProxyHelper.EnableProxyHttp(
Equals(ListeningIpAddress, IPAddress.Any) ? "127.0.0.1" : ListeningIpAddress.ToString(), ListeningPort);
SystemProxyHelper.EnableProxyHttp(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(), endPoint.Port);
#if !DEBUG
FireFoxHelper.AddFirefox();
FireFoxHelper.AddFirefox();
#endif
if (endPoint.EnableSsl)
{
RootCertificateName = RootCertificateName ?? "Titanium_Proxy_Test_Root";
if (EnableSsl)
//If certificate was trusted by the machine
if (certTrusted)
{
RootCertificateName = RootCertificateName ?? "Titanium_Proxy_Test_Root";
//If certificate was trusted by the machine
if (certTrusted)
{
SystemProxyHelper.EnableProxyHttps(
Equals(ListeningIpAddress, IPAddress.Any) ? "127.0.0.1" : ListeningIpAddress.ToString(),
ListeningPort);
}
SystemProxyHelper.EnableProxyHttps(
Equals(endPoint.IpAddress, IPAddress.Any) | Equals(endPoint.IpAddress, IPAddress.Loopback) ? "127.0.0.1" : endPoint.IpAddress.ToString(),
endPoint.Port);
}
}
return true;
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
}
public static void Start()
{
EnableSsl = ProxyEndPoints.Any(x => x.EnableSsl);
if (EnableSsl)
certTrusted = CertManager.CreateTrustedRootCertificate();
foreach (var endPoint in ProxyEndPoints)
{
endPoint.listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
endPoint.listener.Start();
endPoint.Port = ((IPEndPoint)endPoint.listener.LocalEndpoint).Port;
// accept clients asynchronously
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
proxyStarted = true;
}
private static void OnAcceptConnection(IAsyncResult asyn)
{
var endPoint = (ProxyEndPoint)asyn.AsyncState;
var client = endPoint.listener.EndAcceptTcpClient(asyn);
try
{
// Get the listener that handles the client request.
_listener.BeginAcceptTcpClient(OnAcceptConnection, _listener);
var client = _listener.EndAcceptTcpClient(asyn);
Task.Factory.StartNew(() => HandleClient(client));
if (endPoint.GetType() == typeof(TransparentProxyEndPoint))
Task.Factory.StartNew(() => HandleClient(endPoint as TransparentProxyEndPoint, client));
else
Task.Factory.StartNew(() => HandleClient(endPoint as ExplicitProxyEndPoint, client));
}
catch
{
// ignored
}
// Get the listener that handles the client request.
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
public static void Stop()
{
var SetAsSystemProxy = ProxyEndPoints.OfType<ExplicitProxyEndPoint>().Any(x => x.IsSystemProxy);
if (SetAsSystemProxy)
{
SystemProxyHelper.DisableAllProxy();
......@@ -132,7 +169,11 @@ namespace Titanium.Web.Proxy
#endif
}
_listener.Stop();
foreach (var endPoint in ProxyEndPoints)
{
endPoint.listener.Stop();
}
CertManager.Dispose();
}
}
......
......@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy
{
partial class ProxyServer
{
private static void HandleClient(TcpClient client)
private static void HandleClient(ExplicitProxyEndPoint endPoint, TcpClient client)
{
Stream clientStream = client.GetStream();
var clientStreamReader = new CustomBinaryReader(clientStream, Encoding.ASCII);
......@@ -51,10 +51,10 @@ namespace Titanium.Web.Proxy
var httpVersion = httpCmdSplit[2];
var excluded = ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x));
var excluded = endPoint.ExcludedHostNameRegex != null ? endPoint.ExcludedHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;
//Client wants to create a secure tcp tunnel (its a HTTPS request)
if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port == 443)
if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port!=80)
{
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
clientStreamReader.ReadAllLines();
......@@ -68,9 +68,10 @@ namespace Titanium.Web.Proxy
try
{
sslStream = new SslStream(clientStream, true);
//Successfully managed to authenticate the client using the fake certificate
sslStream.AuthenticateAsServer(certificate, false,
SslProtocols.Tls | SslProtocols.Ssl3 | SslProtocols.Ssl2, false);
SupportedProtocols, false);
clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII);
clientStreamWriter = new StreamWriter(sslStream);
......@@ -95,15 +96,18 @@ namespace Titanium.Web.Proxy
{
clientStreamReader.ReadAllLines();
WriteConnectResponse(clientStreamWriter, httpVersion);
TcpHelper.SendRaw(clientStreamReader.BaseStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
TcpHelper.SendRaw(clientStream, null, null, httpRemoteUri.Host, httpRemoteUri.Port,
false);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
return;
}
//Now create the request
HandleHttpSessionRequest(client, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.OriginalString : null);
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? true : false);
}
catch
{
......@@ -111,9 +115,43 @@ namespace Titanium.Web.Proxy
}
}
private static void HandleClient(TransparentProxyEndPoint endPoint, TcpClient client)
{
var sslStream = new SslStream(client.GetStream(), true);
CustomBinaryReader clientStreamReader = null;
StreamWriter clientStreamWriter = null;
var certificate = CertManager.CreateCertificate("127.0.0.1");
try
{
//Successfully managed to authenticate the client using the fake certificate
sslStream.AuthenticateAsServer(certificate, false,
SslProtocols.Tls, false);
clientStreamReader = new CustomBinaryReader(sslStream, Encoding.ASCII);
clientStreamWriter = new StreamWriter(sslStream);
//HTTPS server created - we can now decrypt the client's traffic
}
catch (Exception e)
{
if (sslStream != null)
sslStream.Dispose();
Dispose(client, sslStream, clientStreamReader, clientStreamWriter, null);
return;
}
var httpCmd = clientStreamReader.ReadLine();
//Now create the request
HandleHttpSessionRequest(client, httpCmd, sslStream, clientStreamReader, clientStreamWriter,
true);
}
private static void HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string secureTunnelHostName)
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool IsHttps)
{
TcpConnection connection = null;
string lastRequestHostName = null;
......@@ -135,8 +173,8 @@ namespace Titanium.Web.Proxy
var httpCmdSplit = httpCmd.Split(SpaceSplit, 3);
var httpMethod = httpCmdSplit[0];
var httpRemoteUri =
new Uri(secureTunnelHostName == null ? httpCmdSplit[1] : (secureTunnelHostName + httpCmdSplit[1]));
var httpVersion = httpCmdSplit[2];
Version version;
......@@ -149,11 +187,6 @@ namespace Titanium.Web.Proxy
version = new Version(1, 0);
}
if (httpRemoteUri.Scheme == Uri.UriSchemeHttps)
{
args.IsHttps = true;
}
args.ProxySession.Request.RequestHeaders = new List<HttpHeader>();
......@@ -166,10 +199,13 @@ namespace Titanium.Web.Proxy
SetRequestHeaders(args.ProxySession.Request.RequestHeaders, args.ProxySession);
var httpRemoteUri = new Uri(!IsHttps ? httpCmdSplit[1] : (string.Concat("https://", args.ProxySession.Request.Hostname, httpCmdSplit[1])));
args.IsHttps = IsHttps;
if (args.ProxySession.Request.UpgradeToWebSocket)
{
TcpHelper.SendRaw(clientStreamReader.BaseStream, httpCmd, args.ProxySession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, httpRemoteUri.Scheme == Uri.UriSchemeHttps);
TcpHelper.SendRaw(clientStream, httpCmd, args.ProxySession.Request.RequestHeaders,
httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps);
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
return;
}
......@@ -249,7 +285,7 @@ namespace Titanium.Web.Proxy
Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
break;
}
}
if (connection != null)
......
......@@ -103,7 +103,7 @@ namespace Titanium.Web.Proxy
if (response.Response.ResponseHeaders[i].Value.Contains(";"))
{
response.Response.ContentType = response.Response.ResponseHeaders[i].Value.Split(';')[0].Trim();
response.Response.CharacterSet = response.Response.ResponseHeaders[i].Value.Split(';')[1].ToLower().Replace("charset=", string.Empty).Trim();
response.Response.CharacterSet = response.Response.ResponseHeaders[i].Value.Split(';')[1].Substring(9).Trim();
}
else
response.Response.ContentType = response.Response.ResponseHeaders[i].Value.ToLower().Trim();
......
......@@ -33,7 +33,7 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug-Net45|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\Debug-Net45\</OutputPath>
<DefineConstants>DEBUG</DefineConstants>
<DefineConstants>DEBUG;NET45</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PlatformTarget>AnyCPU</PlatformTarget>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
......@@ -44,6 +44,7 @@
<PlatformTarget>AnyCPU</PlatformTarget>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<DefineConstants>NET45</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Reference Include="Ionic.Zip, Version=1.9.7.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL">
......@@ -85,6 +86,7 @@
<Compile Include="Helpers\CertificateManager.cs" />
<Compile Include="Helpers\Firefox.cs" />
<Compile Include="Helpers\SystemProxy.cs" />
<Compile Include="Models\EndPoint.cs" />
<Compile Include="Network\TcpExtensions.cs" />
<Compile Include="Network\TcpConnectionManager.cs" />
<Compile Include="Models\HttpHeader.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