Commit 142cabcb authored by Jehonathan Thomas's avatar Jehonathan Thomas Committed by GitHub

Merge pull request #194 from justcoding121/develop

Mege Beta with develop
parents 43895859 a229da42
......@@ -31,23 +31,23 @@ namespace Titanium.Web.Proxy.Examples.Basic
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//Useful for clients that use certificate pinning
//for example dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Usefull when certificate trust is not requiered by proxy clients
//Useful when certificate trust is not required by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//An explicit endpoint is where the client knows about the existance of a proxy
//An explicit endpoint is where the client knows about the existence of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
//Transparent endpoint is useful for reverse proxying (client is not aware of the existence of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
//Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
......@@ -81,7 +81,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
proxyServer.Stop();
}
//intecept & cancel, redirect or update requests
//intecept & cancel redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.WebSession.Request.Url);
......
......@@ -25,7 +25,7 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE;NET45</DefineConstants>
......@@ -33,6 +33,7 @@
<WarningLevel>4</WarningLevel>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<Prefer32Bit>false</Prefer32Bit>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
......
......@@ -38,76 +38,76 @@ For stable releases on [stable branch](https://github.com/justcoding121/Titanium
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;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//for example dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Usefull when certificate trust is not requiered by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//An explicit endpoint is where the client knows about the existance of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
//Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
GenericCertificateName = "google.com"
};
proxyServer.AddEndPoint(transparentEndPoint);
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy
proxyServer.TrustRootCertificate = true;
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//Exclude Https addresses you don't want to proxy
//Usefull for clients that use certificate pinning
//for example dropbox.com
// ExcludedHttpsHostNameRegex = new List<string>() { "google.com", "dropbox.com" }
//Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain
//Usefull when certificate trust is not requiered by proxy clients
// GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
};
//An explicit endpoint is where the client knows about the existance of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
//Warning! Transparent endpoint is not tested end to end
//Transparent endpoint is usefull for reverse proxying (client is not aware of the existance of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets to this endpoint
//Currently do not support Server Name Indication (It is not currently supported by SslStream class)
//That means that the transparent endpoint will always provide the same Generic Certificate to all HTTPS requests
//In this example only google.com will work for HTTPS requests
//Other sites will receive a certificate mismatch warning on browser
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{
GenericCertificateName = "google.com"
};
proxyServer.AddEndPoint(transparentEndPoint);
//proxyServer.UpStreamHttpProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
//proxyServer.UpStreamHttpsProxy = new ExternalProxy() { HostName = "localhost", Port = 8888 };
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);
//Only explicit proxies can be set as system proxy!
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
//Only explicit proxies can be set as system proxy!
proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
//wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read();
//Unsubscribe & Quit
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
//Unsubscribe & Quit
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
proxyServer.Stop();
```
Sample request and response event handlers
```csharp
public async Task OnRequest(object sender, SessionEventArgs e)
{
public async Task OnRequest(object sender, SessionEventArgs e)
{
Console.WriteLine(e.WebSession.Request.Url);
////read request headers
......@@ -143,11 +143,11 @@ Sample request and response event handlers
{
await e.Redirect("https://www.paypal.com");
}
}
}
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
//read response headers
var responseHeaders = e.WebSession.Response.ResponseHeaders;
......@@ -166,25 +166,24 @@ Sample request and response event handlers
}
}
}
}
}
/// Allows overriding default certificate validation logic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
/// Allows overriding default certificate validation logic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true;
return Task.FromResult(0);
}
}
/// Allows overriding default client certificate selection logic during mutual authentication
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{
/// 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
============
......
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Titanium.Web.Proxy.IntegrationTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Titanium.Web.Proxy.IntegrationTests")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("32231301-b0fb-4f9e-98df-b3e8a88f4c16")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Net;
using System.Collections.Generic;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Models;
using System.Net.Http;
using System.Diagnostics;
namespace Titanium.Web.Proxy.IntegrationTests
{
[TestClass]
public class SslTests
{
[TestMethod]
public void TestSsl()
{
//expand this to stress test to find
//why in long run proxy becomes unresponsive as per issue #184
var testUrl = "https://google.com";
int proxyPort = 8086;
var proxy = new ProxyTestController();
proxy.StartProxy(proxyPort);
using (var client = CreateHttpClient(testUrl, proxyPort))
{
var response = client.GetAsync(new Uri(testUrl)).Result;
}
}
private HttpClient CreateHttpClient(string url, int localProxyPort)
{
var handler = new HttpClientHandler
{
Proxy = new WebProxy(string.Format("http://localhost:{0}", localProxyPort), false),
UseProxy = true,
};
var client = new HttpClient(handler);
return client;
}
}
public class ProxyTestController
{
private ProxyServer proxyServer;
public ProxyTestController()
{
proxyServer = new ProxyServer();
proxyServer.TrustRootCertificate = true;
}
public void StartProxy(int proxyPort)
{
proxyServer.BeforeRequest += OnRequest;
proxyServer.BeforeResponse += OnResponse;
proxyServer.ServerCertificateValidationCallback += OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, proxyPort, true);
//An explicit endpoint is where the client knows about the existance of a proxy
//So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start();
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);
}
public void Stop()
{
proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse;
proxyServer.ServerCertificateValidationCallback -= OnCertificateValidation;
proxyServer.ClientCertificateSelectionCallback -= OnCertificateSelection;
proxyServer.Stop();
}
//intecept & cancel, redirect or update requests
public async Task OnRequest(object sender, SessionEventArgs e)
{
Debug.WriteLine(e.WebSession.Request.Url);
await Task.FromResult(0);
}
//Modify response
public async Task OnResponse(object sender, SessionEventArgs e)
{
await Task.FromResult(0);
}
/// <summary>
/// Allows overriding default certificate validation logic
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{
//set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
{
e.IsValid = true;
}
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);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Titanium.Web.Proxy.IntegrationTests</RootNamespace>
<AssemblyName>Titanium.Web.Proxy.IntegrationTests</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Net.Http" />
</ItemGroup>
<Choose>
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
</ItemGroup>
</When>
<Otherwise>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
</ItemGroup>
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="SslTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{8d73a1be-868c-42d2-9ece-f32cc1a02906}</Project>
<Name>Titanium.Web.Proxy</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
</Choose>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
......@@ -20,7 +20,7 @@ namespace Titanium.Web.Proxy.UnitTests
{
var tasks = new List<Task>();
var mgr = new CertificateManager("Titanium", "Titanium Root Certificate Authority",
var mgr = new CertificateManager(CertificateEngine.DefaultWindows, "Titanium", "Titanium Root Certificate Authority",
new Lazy<Action<Exception>>(() => (e => { })).Value);
mgr.ClearIdleCertificates(1);
......

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{B6DBABDC-C985-4872-9C38-B4E5079CBC4B}"
EndProject
......@@ -33,6 +33,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTests", "Tests\Titanium.Web.Proxy.UnitTests\Titanium.Web.Proxy.UnitTests.csproj", "{B517E3D0-D03B-436F-AB03-34BA0D5321AF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.IntegrationTests", "Tests\Titanium.Web.Proxy.IntegrationTests\Titanium.Web.Proxy.IntegrationTests.csproj", "{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -51,6 +53,10 @@ Global
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B517E3D0-D03B-436F-AB03-34BA0D5321AF}.Release|Any CPU.Build.0 = Release|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -58,6 +64,7 @@ Global
GlobalSection(NestedProjects) = preSolution
{F3B7E553-1904-4E80-BDC7-212342B5C952} = {B6DBABDC-C985-4872-9C38-B4E5079CBC4B}
{B517E3D0-D03B-436F-AB03-34BA0D5321AF} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
{32231301-B0FB-4F9E-98DF-B3E8A88F4C16} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
......
using System;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
......@@ -26,11 +25,13 @@ namespace Titanium.Web.Proxy
//if user callback is registered then do it
if (ServerCertificateValidationCallback != null)
{
var args = new CertificateValidationEventArgs();
var args = new CertificateValidationEventArgs
{
Certificate = certificate,
Chain = chain,
SslPolicyErrors = sslPolicyErrors
};
args.Certificate = certificate;
args.Chain = chain;
args.SslPolicyErrors = sslPolicyErrors;
Delegate[] invocationList = ServerCertificateValidationCallback.GetInvocationList();
......@@ -73,7 +74,6 @@ namespace Titanium.Web.Proxy
string[] acceptableIssuers)
{
X509Certificate clientCertificate = null;
var customSslStream = sender as SslStream;
if (acceptableIssuers != null &&
acceptableIssuers.Length > 0 &&
......@@ -100,13 +100,15 @@ namespace Titanium.Web.Proxy
//If user call back is registered
if (ClientCertificateSelectionCallback != null)
{
var args = new CertificateSelectionEventArgs();
var args = new CertificateSelectionEventArgs
{
TargetHost = targetHost,
LocalCertificates = localCertificates,
RemoteCertificate = remoteCertificate,
AcceptableIssuers = acceptableIssuers,
ClientCertificate = clientCertificate
};
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];
......
......@@ -13,8 +13,6 @@
return new GZipCompression();
case "deflate":
return new DeflateCompression();
case "zlib":
return new ZlibCompression();
default:
return null;
}
......
using Ionic.Zlib;
using System.IO;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
......
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Compression
{
/// <summary>
/// concrete implementation of zlib compression
/// </summary>
internal class ZlibCompression : ICompression
{
public async Task<byte[]> Compress(byte[] responseBody)
{
using (var ms = new MemoryStream())
{
using (var zip = new ZlibStream(ms, CompressionMode.Compress, true))
{
await zip.WriteAsync(responseBody, 0, responseBody.Length);
}
return ms.ToArray();
}
}
}
}
......@@ -13,8 +13,6 @@
return new GZipDecompression();
case "deflate":
return new DeflateDecompression();
case "zlib":
return new ZlibDecompression();
default:
return new DefaultDecompression();
}
......
using Ionic.Zlib;
using System.IO;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
......@@ -12,8 +11,7 @@ namespace Titanium.Web.Proxy.Decompression
{
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{
var stream = new MemoryStream(compressedArray);
using (var stream = new MemoryStream(compressedArray))
using (var decompressor = new DeflateStream(stream, CompressionMode.Decompress))
{
var buffer = new byte[bufferSize];
......
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
......
using Ionic.Zlib;
using System.IO;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Decompression
{
/// <summary>
/// concrete implemetation of zlib de-compression
/// </summary>
internal class ZlibDecompression : IDecompression
{
public async Task<byte[]> Decompress(byte[] compressedArray, int bufferSize)
{
var memoryStream = new MemoryStream(compressedArray);
using (var decompressor = new ZlibStream(memoryStream, CompressionMode.Decompress))
{
var buffer = new byte[bufferSize];
using (var output = new MemoryStream())
{
int read;
while ((read = await decompressor.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await output.WriteAsync(buffer, 0, read);
}
return output.ToArray();
}
}
}
}
}
......@@ -8,12 +8,29 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public class CertificateSelectionEventArgs : EventArgs
{
/// <summary>
/// Sender object.
/// </summary>
public object Sender { get; internal set; }
/// <summary>
/// Target host.
/// </summary>
public string TargetHost { get; internal set; }
/// <summary>
/// Local certificates.
/// </summary>
public X509CertificateCollection LocalCertificates { get; internal set; }
/// <summary>
/// Remote certificate.
/// </summary>
public X509Certificate RemoteCertificate { get; internal set; }
/// <summary>
/// Acceptable issuers.
/// </summary>
public string[] AcceptableIssuers { get; internal set; }
/// <summary>
/// Client Certificate.
/// </summary>
public X509Certificate ClientCertificate { get; set; }
}
......
......@@ -7,17 +7,24 @@ namespace Titanium.Web.Proxy.EventArguments
/// <summary>
/// An argument passed on to the user for validating the server certificate during SSL authentication
/// </summary>
public class CertificateValidationEventArgs : EventArgs, IDisposable
public class CertificateValidationEventArgs : EventArgs
{
/// <summary>
/// Certificate
/// </summary>
public X509Certificate Certificate { get; internal set; }
/// <summary>
/// Certificate chain
/// </summary>
public X509Chain Chain { get; internal set; }
/// <summary>
/// SSL policy errors.
/// </summary>
public SslPolicyErrors SslPolicyErrors { get; internal set; }
/// <summary>
/// is a valid certificate?
/// </summary>
public bool IsValid { get; set; }
public void Dispose()
{
}
}
}
......@@ -44,7 +44,9 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public Guid Id => WebSession.RequestId;
//Should we send a rerequest
/// <summary>
/// Should we send a rerequest
/// </summary>
public bool ReRequest
{
get;
......@@ -56,7 +58,9 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public bool IsHttps => WebSession.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
/// <summary>
/// Client End Point.
/// </summary>
public IPEndPoint ClientEndPoint => (IPEndPoint)ProxyClient.TcpClient.Client.RemoteEndPoint;
/// <summary>
......@@ -65,8 +69,14 @@ namespace Titanium.Web.Proxy.EventArguments
/// </summary>
public HttpWebClient WebSession { get; set; }
/// <summary>
/// Are we using a custom upstream HTTP proxy?
/// </summary>
public ExternalProxy CustomUpStreamHttpProxyUsed { get; set; }
/// <summary>
/// Are we using a custom upstream HTTPS proxy?
/// </summary>
public ExternalProxy CustomUpStreamHttpsProxyUsed { get; set; }
/// <summary>
......@@ -105,7 +115,7 @@ namespace Titanium.Web.Proxy.EventArguments
//For chunked request we need to read data as they arrive, until we reach a chunk end symbol
if (WebSession.Request.IsChunked)
{
await this.ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
await ProxyClient.ClientStreamReader.CopyBytesToStreamChunked(bufferSize, requestBodyStream);
}
else
{
......@@ -113,7 +123,7 @@ namespace Titanium.Web.Proxy.EventArguments
if (WebSession.Request.ContentLength > 0)
{
//If not chunked then its easy just read the amount of bytes mentioned in content length header of response
await this.ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
await ProxyClient.ClientStreamReader.CopyBytesToStream(bufferSize, requestBodyStream,
WebSession.Request.ContentLength);
}
......@@ -431,7 +441,7 @@ namespace Titanium.Web.Proxy.EventArguments
            await GenericResponse(html, null, status);
}
/// <summary>
/// <summary>
/// Before request is made to server 
/// Respond with the specified HTML string to client
/// and the specified status
......@@ -485,12 +495,17 @@ namespace Titanium.Web.Proxy.EventArguments
WebSession.Request.CancelRequest = true;
}
/// <summary>
/// Redirect to URL.
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task Redirect(string url)
{
var response = new RedirectResponse();
response.HttpVersion = WebSession.Request.HttpVersion;
response.ResponseHeaders.Add("Location", new Models.HttpHeader("Location", url));
response.ResponseHeaders.Add("Location", new HttpHeader("Location", url));
response.ResponseBody = Encoding.ASCII.GetBytes(string.Empty);
await Respond(response);
......
using System;

namespace Titanium.Web.Proxy.Exceptions
{
/// <summary>
......@@ -7,6 +6,10 @@ namespace Titanium.Web.Proxy.Exceptions
/// </summary>
public class BodyNotFoundException : ProxyException
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="message"></param>
public BodyNotFoundException(string message)
: base(message)
{
......
......@@ -2,6 +2,9 @@
namespace Titanium.Web.Proxy.Extensions
{
/// <summary>
/// Extension methods for Byte Arrays.
/// </summary>
public static class ByteArrayExtensions
{
/// <summary>
......@@ -14,7 +17,7 @@ namespace Titanium.Web.Proxy.Extensions
/// <returns></returns>
public static T[] SubArray<T>(this T[] data, int index, int length)
{
T[] result = new T[length];
var result = new T[length];
Array.Copy(data, index, result, 0, length);
return result;
}
......
......@@ -12,11 +12,11 @@ namespace Titanium.Web.Proxy.Extensions
internal static bool IsConnected(this Socket client)
{
// This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking;
var blockingState = client.Blocking;
try
{
byte[] tmp = new byte[1];
var tmp = new byte[1];
client.Blocking = false;
client.Send(tmp, 0, 0);
......@@ -25,14 +25,7 @@ namespace Titanium.Web.Proxy.Extensions
catch (SocketException e)
{
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
{
return true;
}
else
{
return false;
}
return e.NativeErrorCode.Equals(10035);
}
finally
{
......
......@@ -4,7 +4,6 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Shared;
namespace Titanium.Web.Proxy.Helpers
{
......@@ -16,15 +15,15 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
internal class CustomBinaryReader : IDisposable
{
private Stream stream;
private Encoding encoding;
private readonly Stream stream;
private readonly Encoding encoding;
internal CustomBinaryReader(Stream stream)
{
this.stream = stream;
//default to UTF-8
this.encoding = Encoding.UTF8;
encoding = Encoding.UTF8;
}
internal Stream BaseStream => stream;
......@@ -40,7 +39,7 @@ namespace Titanium.Web.Proxy.Helpers
var lastChar = default(char);
var buffer = new byte[1];
while ((await this.stream.ReadAsync(buffer, 0, 1)) > 0)
while ((await stream.ReadAsync(buffer, 0, 1)) > 0)
{
//if new line
if (lastChar == '\r' && buffer[0] == '\n')
......@@ -82,6 +81,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <summary>
/// Read the specified number of raw bytes from the base stream
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="totalBytesToRead"></param>
/// <returns></returns>
internal async Task<byte[]> ReadBytesAsync(int bufferSize, long totalBytesToRead)
......@@ -98,7 +98,7 @@ namespace Titanium.Web.Proxy.Helpers
using (var outStream = new MemoryStream())
{
while ((bytesRead += await this.stream.ReadAsync(buffer, 0, bytesToRead)) > 0)
while ((bytesRead += await stream.ReadAsync(buffer, 0, bytesToRead)) > 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
......
......@@ -8,6 +8,9 @@ namespace Titanium.Web.Proxy.Helpers
/// </summary>
public class FireFoxProxySettingsManager
{
/// <summary>
/// Add Firefox settings.
/// </summary>
public void AddFirefox()
{
try
......@@ -16,28 +19,27 @@ namespace Titanium.Web.Proxy.Helpers
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (File.Exists(myFfPrefFile))
{
if (!File.Exists(myFfPrefFile)) return;
// We have a pref file so let''s make sure it has the proxy setting
var myReader = new StreamReader(myFfPrefFile);
var myPrefContents = myReader.ReadToEnd();
myReader.Close();
if (myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);"))
{
if (!myPrefContents.Contains("user_pref(\"network.proxy.type\", 0);")) return;
// Add the proxy enable line and write it back to the file
myPrefContents = myPrefContents.Replace("user_pref(\"network.proxy.type\", 0);", "");
File.Delete(myFfPrefFile);
File.WriteAllText(myFfPrefFile, myPrefContents);
}
}
}
catch (Exception)
{
// Only exception should be a read/write error because the user opened up FireFox so they can be ignored.
}
}
/// <summary>
/// Remove firefox settings.
/// </summary>
public void RemoveFirefox()
{
try
......@@ -46,8 +48,7 @@ namespace Titanium.Web.Proxy.Helpers
new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) +
"\\Mozilla\\Firefox\\Profiles\\").GetDirectories("*.default");
var myFfPrefFile = myProfileDirectory[0].FullName + "\\prefs.js";
if (File.Exists(myFfPrefFile))
{
if (!File.Exists(myFfPrefFile)) return;
// We have a pref file so let''s make sure it has the proxy setting
var myReader = new StreamReader(myFfPrefFile);
var myPrefContents = myReader.ReadToEnd();
......@@ -61,7 +62,6 @@ namespace Titanium.Web.Proxy.Helpers
File.WriteAllText(myFfPrefFile, myPrefContents);
}
}
}
catch (Exception)
{
// Only exception should be a read/write error because the user opened up FireFox so they can be ignored.
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
......@@ -33,28 +29,14 @@ namespace Titanium.Web.Proxy.Helpers
/// Adapated from below link
/// http://stackoverflow.com/questions/11834091/how-to-check-if-localhost
/// </summary>
/// <param name="address></param>
/// <param name="address"></param>
/// <returns></returns>
internal static bool IsLocalIpAddress(IPAddress address)
{
try
{
// get local IP addresses
IPAddress[] localIPs = Dns.GetHostAddresses(Dns.GetHostName());
var localIPs = Dns.GetHostAddresses(Dns.GetHostName());
// test if any host IP equals to any local IP or to localhost
// is localhost
if (IPAddress.IsLoopback(address)) return true;
// is local address
foreach (IPAddress localIP in localIPs)
{
if (address.Equals(localIP)) return true;
}
}
catch { }
return false;
return IPAddress.IsLoopback(address) || localIPs.Contains(address);
}
}
}
using System;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// Run time helpers
/// </summary>
internal class RunTime
{
/// <summary>
/// Checks if current run time is Mono
/// </summary>
/// <returns></returns>
internal static bool IsRunningOnMono()
{
return Type.GetType("Mono.Runtime") != null;
}
}
}
......@@ -4,11 +4,8 @@ using Microsoft.Win32;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
/// <summary>
/// Helper classes for setting system proxy settings
/// </summary>
// Helper classes for setting system proxy settings
namespace Titanium.Web.Proxy.Helpers
{
internal enum ProxyProtocolType
......
......@@ -140,6 +140,7 @@ namespace Titanium.Web.Proxy.Helpers
/// <param name="localCertificateSelectionCallback"></param>
/// <param name="clientStream"></param>
/// <param name="tcpConnectionFactory"></param>
/// <param name="upStreamEndPoint"></param>
/// <returns></returns>
internal static async Task SendRaw(int bufferSize, int connectionTimeOutSeconds,
string remoteHostName, int remotePort, string httpCmd, Version httpVersion, Dictionary<string, HttpHeader> requestHeaders,
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network;
using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared;
......@@ -15,17 +13,26 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public class HttpWebClient
{
/// <summary>
/// Connection to server
/// </summary>
internal TcpConnection ServerConnection { get; set; }
public Guid RequestId { get; private set; }
/// <summary>
/// Request ID.
/// </summary>
public Guid RequestId { get; }
/// <summary>
/// Headers passed with Connect.
/// </summary>
public List<HttpHeader> ConnectHeaders { get; set; }
/// <summary>
/// Web Request.
/// </summary>
public Request Request { get; set; }
/// <summary>
/// Web Response.
/// </summary>
public Response Response { get; set; }
/// <summary>
......@@ -37,15 +44,14 @@ namespace Titanium.Web.Proxy.Http
/// <summary>
/// Is Https?
/// </summary>
public bool IsHttps => this.Request.RequestUri.Scheme == Uri.UriSchemeHttps;
public bool IsHttps => Request.RequestUri.Scheme == Uri.UriSchemeHttps;
internal HttpWebClient()
{
this.RequestId = Guid.NewGuid();
this.Request = new Request();
this.Response = new Response();
RequestId = Guid.NewGuid();
Request = new Request();
Response = new Response();
}
/// <summary>
......@@ -65,18 +71,18 @@ namespace Titanium.Web.Proxy.Http
/// <returns></returns>
internal async Task SendRequest(bool enable100ContinueBehaviour)
{
Stream stream = ServerConnection.Stream;
var stream = ServerConnection.Stream;
StringBuilder requestLines = new StringBuilder();
var requestLines = new StringBuilder();
//prepare the request & headers
if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps == true))
if ((ServerConnection.UpStreamHttpProxy != null && ServerConnection.IsHttps == false) || (ServerConnection.UpStreamHttpsProxy != null && ServerConnection.IsHttps))
{
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.AbsoluteUri, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
requestLines.AppendLine(string.Join(" ", Request.Method, Request.RequestUri.AbsoluteUri, $"HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"));
}
else
{
requestLines.AppendLine(string.Join(" ", this.Request.Method, this.Request.RequestUri.PathAndQuery, $"HTTP/{this.Request.HttpVersion.Major}.{this.Request.HttpVersion.Minor}"));
requestLines.AppendLine(string.Join(" ", Request.Method, Request.RequestUri.PathAndQuery, $"HTTP/{Request.HttpVersion.Major}.{Request.HttpVersion.Minor}"));
}
//Send Authentication to Upstream proxy if needed
......@@ -86,7 +92,7 @@ namespace Titanium.Web.Proxy.Http
requestLines.AppendLine("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(ServerConnection.UpStreamHttpProxy.UserName + ":" + ServerConnection.UpStreamHttpProxy.Password)));
}
//write request headers
foreach (var headerItem in this.Request.RequestHeaders)
foreach (var headerItem in Request.RequestHeaders)
{
var header = headerItem.Value;
if (headerItem.Key != "Proxy-Authorization")
......@@ -96,7 +102,7 @@ namespace Titanium.Web.Proxy.Http
}
//write non unique request headers
foreach (var headerItem in this.Request.NonUniqueRequestHeaders)
foreach (var headerItem in Request.NonUniqueRequestHeaders)
{
var headers = headerItem.Value;
foreach (var header in headers)
......@@ -110,15 +116,15 @@ namespace Titanium.Web.Proxy.Http
requestLines.AppendLine();
string request = requestLines.ToString();
byte[] requestBytes = Encoding.ASCII.GetBytes(request);
var request = requestLines.ToString();
var requestBytes = Encoding.ASCII.GetBytes(request);
await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
await stream.FlushAsync();
if (enable100ContinueBehaviour)
{
if (this.Request.ExpectContinue)
if (Request.ExpectContinue)
{
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
var responseStatusCode = httpResult[1].Trim();
......@@ -128,13 +134,13 @@ namespace Titanium.Web.Proxy.Http
if (responseStatusCode.Equals("100")
&& responseStatusDescription.ToLower().Equals("continue"))
{
this.Request.Is100Continue = true;
Request.Is100Continue = true;
await ServerConnection.StreamReader.ReadLineAsync();
}
else if (responseStatusCode.Equals("417")
&& responseStatusDescription.ToLower().Equals("expectation failed"))
{
this.Request.ExpectationFailed = true;
Request.ExpectationFailed = true;
await ServerConnection.StreamReader.ReadLineAsync();
}
}
......@@ -148,7 +154,7 @@ namespace Titanium.Web.Proxy.Http
internal async Task ReceiveResponse()
{
//return if this is already read
if (this.Response.ResponseStatusCode != null) return;
if (Response.ResponseStatusCode != null) return;
var httpResult = (await ServerConnection.StreamReader.ReadLineAsync()).Split(ProxyConstants.SpaceSplit, 3);
......@@ -161,33 +167,33 @@ namespace Titanium.Web.Proxy.Http
var httpVersion = httpResult[0].Trim().ToLower();
var version = new Version(1, 1);
if (httpVersion == "http/1.0")
if (0 == string.CompareOrdinal(httpVersion, "http/1.0"))
{
version = new Version(1, 0);
}
this.Response.HttpVersion = version;
this.Response.ResponseStatusCode = httpResult[1].Trim();
this.Response.ResponseStatusDescription = httpResult[2].Trim();
Response.HttpVersion = version;
Response.ResponseStatusCode = httpResult[1].Trim();
Response.ResponseStatusDescription = httpResult[2].Trim();
//For HTTP 1.1 comptibility server may send expect-continue even if not asked for it in request
if (this.Response.ResponseStatusCode.Equals("100")
&& this.Response.ResponseStatusDescription.ToLower().Equals("continue"))
if (Response.ResponseStatusCode.Equals("100")
&& Response.ResponseStatusDescription.ToLower().Equals("continue"))
{
//Read the next line after 100-continue
this.Response.Is100Continue = true;
this.Response.ResponseStatusCode = null;
Response.Is100Continue = true;
Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
return;
}
else if (this.Response.ResponseStatusCode.Equals("417")
&& this.Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
else if (Response.ResponseStatusCode.Equals("417")
&& Response.ResponseStatusDescription.ToLower().Equals("expectation failed"))
{
//read next line after expectation failed response
this.Response.ExpectationFailed = true;
this.Response.ResponseStatusCode = null;
Response.ExpectationFailed = true;
Response.ResponseStatusCode = null;
await ServerConnection.StreamReader.ReadLineAsync();
//now receive response
await ReceiveResponse();
......
......@@ -34,12 +34,7 @@ namespace Titanium.Web.Proxy.Http
get
{
var hasHeader = RequestHeaders.ContainsKey("host");
if (hasHeader)
{
return RequestHeaders["host"].Value;
}
return null;
return hasHeader ? RequestHeaders["host"].Value : null;
}
set
{
......@@ -154,11 +149,11 @@ namespace Titanium.Web.Proxy.Http
if (hasHeader)
{
var header = RequestHeaders["content-type"];
header.Value = value.ToString();
header.Value = value;
}
else
{
RequestHeaders.Add("content-type", new HttpHeader("content-type", value.ToString()));
RequestHeaders.Add("content-type", new HttpHeader("content-type", value));
}
}
......@@ -219,15 +214,11 @@ namespace Titanium.Web.Proxy.Http
{
var hasHeader = RequestHeaders.ContainsKey("expect");
if (hasHeader)
{
if (!hasHeader) return false;
var header = RequestHeaders["expect"];
return header.Value.Equals("100-continue");
}
return false;
}
}
/// <summary>
......@@ -275,13 +266,7 @@ namespace Titanium.Web.Proxy.Http
var header = RequestHeaders["upgrade"];
if (header.Value.ToLower() == "websocket")
{
return true;
}
return false;
return header.Value.ToLower() == "websocket";
}
}
......@@ -305,6 +290,9 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public bool ExpectationFailed { get; internal set; }
/// <summary>
/// Constructor.
/// </summary>
public Request()
{
RequestHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
......
......@@ -12,7 +12,13 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public class Response
{
/// <summary>
/// Response Status Code.
/// </summary>
public string ResponseStatusCode { get; set; }
/// <summary>
/// Response Status description.
/// </summary>
public string ResponseStatusDescription { get; set; }
internal Encoding Encoding => this.GetResponseCharacterEncoding();
......@@ -26,15 +32,11 @@ namespace Titanium.Web.Proxy.Http
{
var hasHeader = ResponseHeaders.ContainsKey("content-encoding");
if (hasHeader)
{
if (!hasHeader) return null;
var header = ResponseHeaders["content-encoding"];
return header.Value.Trim();
}
return null;
}
}
internal Version HttpVersion { get; set; }
......@@ -229,10 +231,13 @@ namespace Titanium.Web.Proxy.Http
/// </summary>
public bool ExpectationFailed { get; internal set; }
/// <summary>
/// Constructor.
/// </summary>
public Response()
{
this.ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
this.NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
ResponseHeaders = new Dictionary<string, HttpHeader>(StringComparer.OrdinalIgnoreCase);
NonUniqueResponseHeaders = new Dictionary<string, List<HttpHeader>>(StringComparer.OrdinalIgnoreCase);
}
}
......
......@@ -7,16 +7,25 @@ namespace Titanium.Web.Proxy.Http.Responses
    /// </summary>
    public class GenericResponse : Response
    {
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="status"></param>
        public GenericResponse(HttpStatusCode status)
        {
            ResponseStatusCode = ((int)status).ToString();
            ResponseStatusDescription = status.ToString(); 
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="statusCode"></param>
        /// <param name="statusDescription"></param>
        public GenericResponse(string statusCode, string statusDescription)
        {
            ResponseStatusCode = statusCode;
            ResponseStatusDescription = statusCode;
            ResponseStatusDescription = statusDescription;
        }
    }
}
......@@ -5,6 +5,9 @@
/// </summary>
public sealed class OkResponse : Response
{
/// <summary>
/// Constructor.
/// </summary>
public OkResponse()
{
ResponseStatusCode = "200";
......
......@@ -5,6 +5,9 @@
/// </summary>
public sealed class RedirectResponse : Response
{
/// <summary>
/// Constructor.
/// </summary>
public RedirectResponse()
{
ResponseStatusCode = "302";
......
......@@ -10,20 +10,38 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public abstract class ProxyEndPoint
{
public ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
/// <summary>
/// Constructor.
/// </summary>
/// <param name="IpAddress"></param>
/// <param name="Port"></param>
/// <param name="EnableSsl"></param>
protected ProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
{
this.IpAddress = IpAddress;
this.Port = Port;
this.EnableSsl = EnableSsl;
}
/// <summary>
/// Ip Address.
/// </summary>
public IPAddress IpAddress { get; internal set; }
/// <summary>
/// Port.
/// </summary>
public int Port { get; internal set; }
/// <summary>
/// Enable SSL?
/// </summary>
public bool EnableSsl { get; internal set; }
public bool IpV6Enabled => IpAddress == IPAddress.IPv6Any
|| IpAddress == IPAddress.IPv6Loopback
|| IpAddress == IPAddress.IPv6None;
/// <summary>
/// Is IPv6 enabled?
/// </summary>
public bool IpV6Enabled => Equals(IpAddress, IPAddress.IPv6Any)
|| Equals(IpAddress, IPAddress.IPv6Loopback)
|| Equals(IpAddress, IPAddress.IPv6None);
internal TcpListener listener { get; set; }
}
......@@ -37,10 +55,22 @@ namespace Titanium.Web.Proxy.Models
internal bool IsSystemHttpProxy { get; set; }
internal bool IsSystemHttpsProxy { get; set; }
/// <summary>
/// List of host names to exclude using Regular Expressions.
/// </summary>
public List<string> ExcludedHttpsHostNameRegex { get; set; }
/// <summary>
/// Generic certificate to use for SSL decryption.
/// </summary>
public X509Certificate2 GenericCertificate { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="IpAddress"></param>
/// <param name="Port"></param>
/// <param name="EnableSsl"></param>
public ExplicitProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
......@@ -54,13 +84,19 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class TransparentProxyEndPoint : ProxyEndPoint
{
//Name of the Certificate need to be sent (same as the hostname we want to proxy)
//This is valid only when UseServerNameIndication is set to false
public string GenericCertificateName { get; set; }
// public bool UseServerNameIndication { get; set; }
/// <summary>
/// Name of the Certificate need to be sent (same as the hostname we want to proxy)
/// This is valid only when UseServerNameIndication is set to false
/// </summary>
public string GenericCertificateName { get; set; }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="IpAddress"></param>
/// <param name="Port"></param>
/// <param name="EnableSsl"></param>
public TransparentProxyEndPoint(IPAddress IpAddress, int Port, bool EnableSsl)
: base(IpAddress, Port, EnableSsl)
{
......
......@@ -13,8 +13,14 @@ namespace Titanium.Web.Proxy.Models
private string userName;
private string password;
/// <summary>
/// Use default windows credentials?
/// </summary>
public bool UseDefaultCredentials { get; set; }
/// <summary>
/// Username.
/// </summary>
public string UserName {
get { return UseDefaultCredentials ? DefaultCredentials.Value.UserName : userName; }
set
......@@ -28,6 +34,9 @@ namespace Titanium.Web.Proxy.Models
}
}
/// <summary>
/// Password.
/// </summary>
public string Password
{
get { return UseDefaultCredentials ? DefaultCredentials.Value.Password : password; }
......@@ -42,7 +51,13 @@ namespace Titanium.Web.Proxy.Models
}
}
/// <summary>
/// Host name.
/// </summary>
public string HostName { get; set; }
/// <summary>
/// Port.
/// </summary>
public int Port { get; set; }
}
}
......@@ -7,6 +7,12 @@ namespace Titanium.Web.Proxy.Models
/// </summary>
public class HttpHeader
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <exception cref="Exception"></exception>
public HttpHeader(string name, string value)
{
if (string.IsNullOrEmpty(name))
......@@ -18,7 +24,13 @@ namespace Titanium.Web.Proxy.Models
Value = value.Trim();
}
/// <summary>
/// Header Name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Header Value.
/// </summary>
public string Value { get; set; }
/// <summary>
......@@ -27,7 +39,7 @@ namespace Titanium.Web.Proxy.Models
/// <returns></returns>
public override string ToString()
{
return string.Format("{0}: {1}", Name, Value);
return $"{Name}: {Value}";
}
}
}
\ No newline at end of file
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Implements certificate generation operations.
/// </summary>
public class BCCertificateMaker : ICertificateMaker
{
private const int CertificateValidDays = 1825;
private const int CertificateGraceDays = 366;
/// <summary>
/// Makes the certificate.
/// </summary>
/// <param name="sSubjectCn">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="signingCert">The signing cert.</param>
/// <returns>X509Certificate2 instance.</returns>
public X509Certificate2 MakeCertificate(string sSubjectCn, bool isRoot, X509Certificate2 signingCert = null)
{
return MakeCertificateInternal(sSubjectCn, isRoot, true, signingCert);
}
/// <summary>
/// Generates the certificate.
/// </summary>
/// <param name="subjectName">Name of the subject.</param>
/// <param name="issuerName">Name of the issuer.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="keyStrength">The key strength.</param>
/// <param name="signatureAlgorithm">The signature algorithm.</param>
/// <param name="issuerPrivateKey">The issuer private key.</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="PemException">Malformed sequence in RSA private key</exception>
private static X509Certificate2 GenerateCertificate(string subjectName,
string issuerName, DateTime validFrom,
DateTime validTo, int keyStrength = 2048,
string signatureAlgorithm = "SHA256WithRSA",
AsymmetricKeyParameter issuerPrivateKey = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var secureRandom = new SecureRandom(randomGenerator);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), secureRandom);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDn = new X509Name(subjectName);
var issuerDn = new X509Name(issuerName);
certificateGenerator.SetIssuerDN(issuerDn);
certificateGenerator.SetSubjectDN(subjectDn);
certificateGenerator.SetNotBefore(validFrom);
certificateGenerator.SetNotAfter(validTo);
// Subject Public Key
var keyGenerationParameters = new KeyGenerationParameters(secureRandom, keyStrength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
// Set certificate intended purposes to only Server Authentication
certificateGenerator.AddExtension(X509Extensions.ExtendedKeyUsage.Id, false, new ExtendedKeyUsage(KeyPurposeID.IdKPServerAuth));
var signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerPrivateKey ?? subjectKeyPair.Private, secureRandom);
// Self-sign the certificate
var certificate = certificateGenerator.Generate(signatureFactory);
var x509Certificate = new X509Certificate2(certificate.GetEncoded());
// Corresponding private key
var privateKeyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
var seq = (Asn1Sequence)Asn1Object.FromByteArray(privateKeyInfo.ParsePrivateKey().GetDerEncoded());
if (seq.Count != 9)
{
throw new PemException("Malformed sequence in RSA private key");
}
var rsa = RsaPrivateKeyStructure.GetInstance(seq);
var rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
// Set private key onto certificate instance
x509Certificate.PrivateKey = DotNetUtilities.ToRSA(rsaparams);
x509Certificate.FriendlyName = subjectName;
return x509Certificate;
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="fullSubject">The full subject.</param>
/// <param name="validFrom">The valid from.</param>
/// <param name="validTo">The valid to.</param>
/// <param name="signingCertificate">The signing certificate.</param>
/// <returns>X509Certificate2 instance.</returns>
/// <exception cref="System.ArgumentException">You must specify a Signing Certificate if and only if you are not creating a root.</exception>
private X509Certificate2 MakeCertificateInternal(bool isRoot, string fullSubject, DateTime validFrom, DateTime validTo, X509Certificate2 signingCertificate)
{
if (isRoot != (null == signingCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", nameof(signingCertificate));
}
return isRoot
? GenerateCertificate(fullSubject, fullSubject, validFrom, validTo)
: GenerateCertificate(fullSubject, signingCertificate.Subject, validFrom, validTo, issuerPrivateKey: DotNetUtilities.GetKeyPair(signingCertificate.PrivateKey).Private);
}
/// <summary>
/// Makes the certificate internal.
/// </summary>
/// <param name="subject">The s subject cn.</param>
/// <param name="isRoot">if set to <c>true</c> [is root].</param>
/// <param name="switchToMtaIfNeeded">if set to <c>true</c> [switch to MTA if needed].</param>
/// <param name="signingCert">The signing cert.</param>
/// <returns>X509Certificate2.</returns>
private X509Certificate2 MakeCertificateInternal(string subject, bool isRoot, bool switchToMtaIfNeeded, X509Certificate2 signingCert = null, CancellationToken cancellationToken = default(CancellationToken))
{
X509Certificate2 certificate = null;
if (switchToMtaIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
using (var manualResetEvent = new ManualResetEventSlim(false))
{
ThreadPool.QueueUserWorkItem(o =>
{
certificate = MakeCertificateInternal(subject, isRoot, false, signingCert);
if (!cancellationToken.IsCancellationRequested)
{
manualResetEvent?.Set();
}
});
manualResetEvent.Wait(TimeSpan.FromMinutes(1), cancellationToken: cancellationToken);
}
return certificate;
}
return MakeCertificateInternal(isRoot, $"CN={subject}", DateTime.UtcNow.AddDays(-CertificateGraceDays), DateTime.UtcNow.AddDays(CertificateValidDays), isRoot ? null : signingCert);
}
}
}
\ No newline at end of file
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Abstract interface for different Certificate Maker Engines
/// </summary>
internal interface ICertificateMaker
{
X509Certificate2 MakeCertificate(string sSubjectCn, bool isRoot, X509Certificate2 signingCert);
}
}
using System;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace Titanium.Web.Proxy.Network.Certificate
{
/// <summary>
/// Certificate Maker - uses MakeCert
/// </summary>
public class WinCertificateMaker: ICertificateMaker
{
private readonly Type typeX500DN;
private readonly Type typeX509PrivateKey;
private readonly Type typeOID;
private readonly Type typeOIDS;
private readonly Type typeKUExt;
private readonly Type typeEKUExt;
private readonly Type typeRequestCert;
private readonly Type typeX509Extensions;
private readonly Type typeBasicConstraints;
private readonly Type typeSignerCertificate;
private readonly Type typeX509Enrollment;
private readonly string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";
private object _SharedPrivateKey;
/// <summary>
/// Constructor.
/// </summary>
public WinCertificateMaker()
{
typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true);
typeOID = Type.GetTypeFromProgID("X509Enrollment.CObjectId", true);
typeOIDS = Type.GetTypeFromProgID("X509Enrollment.CObjectIds.1", true);
typeEKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionEnhancedKeyUsage");
typeKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionKeyUsage");
typeRequestCert = Type.GetTypeFromProgID("X509Enrollment.CX509CertificateRequestCertificate");
typeX509Extensions = Type.GetTypeFromProgID("X509Enrollment.CX509Extensions");
typeBasicConstraints = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionBasicConstraints");
typeSignerCertificate = Type.GetTypeFromProgID("X509Enrollment.CSignerCertificate");
typeX509Enrollment = Type.GetTypeFromProgID("X509Enrollment.CX509Enrollment");
}
/// <summary>
/// Make certificate.
/// </summary>
/// <param name="sSubjectCN"></param>
/// <param name="isRoot"></param>
/// <param name="signingCert"></param>
/// <returns></returns>
public X509Certificate2 MakeCertificate(string sSubjectCN, bool isRoot,X509Certificate2 signingCert=null)
{
return MakeCertificateInternal(sSubjectCN, isRoot, true, signingCert);
}
private X509Certificate2 MakeCertificate(bool IsRoot, string FullSubject, int PrivateKeyLength, string HashAlg, DateTime ValidFrom, DateTime ValidTo, X509Certificate2 SigningCertificate)
{
if (IsRoot != (null == SigningCertificate))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", nameof(IsRoot));
}
var x500DN = Activator.CreateInstance(typeX500DN);
var subject = new object[] { FullSubject, 0 };
typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500DN, subject);
var x500DN2 = Activator.CreateInstance(typeX500DN);
if (!IsRoot)
{
subject[0] = SigningCertificate.Subject;
}
typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500DN2, subject);
object sharedPrivateKey = null;
if (!IsRoot)
{
sharedPrivateKey = _SharedPrivateKey;
}
if (sharedPrivateKey == null)
{
sharedPrivateKey = Activator.CreateInstance(typeX509PrivateKey);
subject = new object[] { sProviderName };
typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
subject[0] = 2;
typeX509PrivateKey.InvokeMember("ExportPolicy", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
subject = new object[] { (IsRoot ? 2 : 1) };
typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
if (!IsRoot)
{
subject = new object[] { 176 };
typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
}
subject[0] = PrivateKeyLength;
typeX509PrivateKey.InvokeMember("Length", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
typeX509PrivateKey.InvokeMember("Create", BindingFlags.InvokeMethod, null, sharedPrivateKey, null);
if (!IsRoot)
{
_SharedPrivateKey = sharedPrivateKey;
}
}
subject = new object[1];
var obj3 = Activator.CreateInstance(typeOID);
subject[0] = "1.3.6.1.5.5.7.3.1";
typeOID.InvokeMember("InitializeFromValue", BindingFlags.InvokeMethod, null, obj3, subject);
var obj4 = Activator.CreateInstance(typeOIDS);
subject[0] = obj3;
typeOIDS.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj4, subject);
var obj5 = Activator.CreateInstance(typeEKUExt);
subject[0] = obj4;
typeEKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj5, subject);
var obj6 = Activator.CreateInstance(typeRequestCert);
subject = new[] { 1, sharedPrivateKey, string.Empty };
typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, obj6, subject);
subject = new[] { x500DN };
typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = x500DN;
typeRequestCert.InvokeMember("Issuer", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = ValidFrom;
typeRequestCert.InvokeMember("NotBefore", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = ValidTo;
typeRequestCert.InvokeMember("NotAfter", BindingFlags.PutDispProperty, null, obj6, subject);
var obj7 = Activator.CreateInstance(typeKUExt);
subject[0] = 176;
typeKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj7, subject);
var obj8 = typeRequestCert.InvokeMember("X509Extensions", BindingFlags.GetProperty, null, obj6, null);
subject = new object[1];
if (!IsRoot)
{
subject[0] = obj7;
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
}
subject[0] = obj5;
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
if (!IsRoot)
{
var obj12 = Activator.CreateInstance(typeSignerCertificate);
subject = new object[] { 0, 0, 12, SigningCertificate.Thumbprint };
typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, obj12, subject);
subject = new[] { obj12 };
typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, obj6, subject);
}
else
{
var obj13 = Activator.CreateInstance(typeBasicConstraints);
subject = new object[] { "true", "0" };
typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj13, subject);
subject = new[] { obj13 };
typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
}
var obj14 = Activator.CreateInstance(typeOID);
subject = new object[] { 1, 0, 0, HashAlg };
typeOID.InvokeMember("InitializeFromAlgorithmName", BindingFlags.InvokeMethod, null, obj14, subject);
subject = new[] { obj14 };
typeRequestCert.InvokeMember("HashAlgorithm", BindingFlags.PutDispProperty, null, obj6, subject);
typeRequestCert.InvokeMember("Encode", BindingFlags.InvokeMethod, null, obj6, null);
var obj15 = Activator.CreateInstance(typeX509Enrollment);
subject[0] = obj6;
typeX509Enrollment.InvokeMember("InitializeFromRequest", BindingFlags.InvokeMethod, null, obj15, subject);
if (IsRoot)
{
subject[0] = "DO_NOT_TRUST_TitaniumProxy-CE";
typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null, obj15, subject);
}
subject[0] = 0;
var obj16 = typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, obj15, subject);
subject = new[] { 2, obj16, 0, string.Empty };
typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, obj15, subject);
subject = new object[] { null, 0, 1 };
try
{
var empty = (string)typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, obj15, subject);
return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception)
{
// ignored
}
return null;
}
private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot, bool switchToMTAIfNeeded,X509Certificate2 signingCert=null)
{
X509Certificate2 rCert=null;
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
var manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(o =>
{
rCert = MakeCertificateInternal(sSubjectCN, isRoot, false,signingCert);
manualResetEvent.Set();
});
manualResetEvent.WaitOne();
manualResetEvent.Close();
return rCert;
}
//Subject
var fullSubject = $"CN={sSubjectCN}";
//Sig Algo
var HashAlgo = "SHA256";
//Grace Days
var GraceDays = -366;
//ValiDays
var ValidDays = 1825;
//KeyLength
var keyLength = 2048;
var graceTime = DateTime.Now.AddDays(GraceDays);
var now = DateTime.Now;
rCert = !isRoot ? MakeCertificate(false, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), signingCert) :
MakeCertificate(true, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays(ValidDays), null);
return rCert;
}
}
}
using System;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace Titanium.Web.Proxy.Network
{
public class CertificateMaker
{
private Type typeX500DN;
private Type typeX509PrivateKey;
private Type typeOID;
private Type typeOIDS;
private Type typeKUExt;
private Type typeEKUExt;
private Type typeRequestCert;
private Type typeX509Extensions;
private Type typeBasicConstraints;
private Type typeSignerCertificate;
private Type typeX509Enrollment;
private Type typeAlternativeName;
private Type typeAlternativeNames;
private Type typeAlternativeNamesExt;
private string sProviderName = "Microsoft Enhanced Cryptographic Provider v1.0";
private object _SharedPrivateKey;
public CertificateMaker()
{
this.typeX500DN = Type.GetTypeFromProgID("X509Enrollment.CX500DistinguishedName", true);
this.typeX509PrivateKey = Type.GetTypeFromProgID("X509Enrollment.CX509PrivateKey", true);
this.typeOID = Type.GetTypeFromProgID("X509Enrollment.CObjectId", true);
this.typeOIDS = Type.GetTypeFromProgID("X509Enrollment.CObjectIds.1", true);
this.typeEKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionEnhancedKeyUsage");
this.typeKUExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionKeyUsage");
this.typeRequestCert = Type.GetTypeFromProgID("X509Enrollment.CX509CertificateRequestCertificate");
this.typeX509Extensions = Type.GetTypeFromProgID("X509Enrollment.CX509Extensions");
this.typeBasicConstraints = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionBasicConstraints");
this.typeSignerCertificate = Type.GetTypeFromProgID("X509Enrollment.CSignerCertificate");
this.typeX509Enrollment = Type.GetTypeFromProgID("X509Enrollment.CX509Enrollment");
this.typeAlternativeName = Type.GetTypeFromProgID("X509Enrollment.CAlternativeName");
this.typeAlternativeNames = Type.GetTypeFromProgID("X509Enrollment.CAlternativeNames");
this.typeAlternativeNamesExt = Type.GetTypeFromProgID("X509Enrollment.CX509ExtensionAlternativeNames");
}
public X509Certificate2 MakeCertificate(string sSubjectCN, bool isRoot,X509Certificate2 signingCert=null)
{
return this.MakeCertificateInternal(sSubjectCN, isRoot, true, signingCert);
}
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))
{
throw new ArgumentException("You must specify a Signing Certificate if and only if you are not creating a root.", "oSigningCertificate");
}
object x500DN = Activator.CreateInstance(this.typeX500DN);
object[] subject = new object[] { FullSubject, 0 };
this.typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500DN, subject);
object x500DN2 = Activator.CreateInstance(this.typeX500DN);
if (!IsRoot)
{
subject[0] = SigningCertificate.Subject;
}
this.typeX500DN.InvokeMember("Encode", BindingFlags.InvokeMethod, null, x500DN2, subject);
object sharedPrivateKey = null;
if (!IsRoot)
{
sharedPrivateKey = this._SharedPrivateKey;
}
if (sharedPrivateKey == null)
{
sharedPrivateKey = Activator.CreateInstance(this.typeX509PrivateKey);
subject = new object[] { this.sProviderName };
this.typeX509PrivateKey.InvokeMember("ProviderName", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
subject[0] = 2;
this.typeX509PrivateKey.InvokeMember("ExportPolicy", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
subject = new object[] { (IsRoot ? 2 : 1) };
this.typeX509PrivateKey.InvokeMember("KeySpec", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
if (!IsRoot)
{
subject = new object[] { 176 };
this.typeX509PrivateKey.InvokeMember("KeyUsage", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
}
subject[0] = PrivateKeyLength;
this.typeX509PrivateKey.InvokeMember("Length", BindingFlags.PutDispProperty, null, sharedPrivateKey, subject);
this.typeX509PrivateKey.InvokeMember("Create", BindingFlags.InvokeMethod, null, sharedPrivateKey, null);
if (!IsRoot)
{
this._SharedPrivateKey = sharedPrivateKey;
}
}
subject = new object[1];
object obj3 = Activator.CreateInstance(this.typeOID);
subject[0] = "1.3.6.1.5.5.7.3.1";
this.typeOID.InvokeMember("InitializeFromValue", BindingFlags.InvokeMethod, null, obj3, subject);
object obj4 = Activator.CreateInstance(this.typeOIDS);
subject[0] = obj3;
this.typeOIDS.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj4, subject);
object obj5 = Activator.CreateInstance(this.typeEKUExt);
subject[0] = obj4;
this.typeEKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj5, subject);
object obj6 = Activator.CreateInstance(this.typeRequestCert);
subject = new object[] { 1, sharedPrivateKey, string.Empty };
this.typeRequestCert.InvokeMember("InitializeFromPrivateKey", BindingFlags.InvokeMethod, null, obj6, subject);
subject = new object[] { x500DN };
this.typeRequestCert.InvokeMember("Subject", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = x500DN;
this.typeRequestCert.InvokeMember("Issuer", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = ValidFrom;
this.typeRequestCert.InvokeMember("NotBefore", BindingFlags.PutDispProperty, null, obj6, subject);
subject[0] = ValidTo;
this.typeRequestCert.InvokeMember("NotAfter", BindingFlags.PutDispProperty, null, obj6, subject);
object obj7 = Activator.CreateInstance(this.typeKUExt);
subject[0] = 176;
this.typeKUExt.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj7, subject);
object obj8 = this.typeRequestCert.InvokeMember("X509Extensions", BindingFlags.GetProperty, null, obj6, null);
subject = new object[1];
if (!IsRoot)
{
subject[0] = obj7;
this.typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
}
subject[0] = obj5;
this.typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
if (!IsRoot)
{
object obj12 = Activator.CreateInstance(this.typeSignerCertificate);
subject = new object[] { 0, 0, 12, SigningCertificate.Thumbprint };
this.typeSignerCertificate.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, obj12, subject);
subject = new object[] { obj12 };
this.typeRequestCert.InvokeMember("SignerCertificate", BindingFlags.PutDispProperty, null, obj6, subject);
}
else
{
object obj13 = Activator.CreateInstance(this.typeBasicConstraints);
subject = new object[] { "true", "0" };
this.typeBasicConstraints.InvokeMember("InitializeEncode", BindingFlags.InvokeMethod, null, obj13, subject);
subject = new object[] { obj13 };
this.typeX509Extensions.InvokeMember("Add", BindingFlags.InvokeMethod, null, obj8, subject);
}
object obj14 = Activator.CreateInstance(this.typeOID);
subject = new object[] { 1, 0, 0, HashAlg };
this.typeOID.InvokeMember("InitializeFromAlgorithmName", BindingFlags.InvokeMethod, null, obj14, subject);
subject = new object[] { obj14 };
this.typeRequestCert.InvokeMember("HashAlgorithm", BindingFlags.PutDispProperty, null, obj6, subject);
this.typeRequestCert.InvokeMember("Encode", BindingFlags.InvokeMethod, null, obj6, null);
object obj15 = Activator.CreateInstance(this.typeX509Enrollment);
subject[0] = obj6;
this.typeX509Enrollment.InvokeMember("InitializeFromRequest", BindingFlags.InvokeMethod, null, obj15, subject);
if (IsRoot)
{
subject[0] = "DO_NOT_TRUST_TitaniumProxy-CE";
this.typeX509Enrollment.InvokeMember("CertificateFriendlyName", BindingFlags.PutDispProperty, null, obj15, subject);
}
subject[0] = 0;
object obj16 = this.typeX509Enrollment.InvokeMember("CreateRequest", BindingFlags.InvokeMethod, null, obj15, subject);
subject = new object[] { 2, obj16, 0, string.Empty };
this.typeX509Enrollment.InvokeMember("InstallResponse", BindingFlags.InvokeMethod, null, obj15, subject);
subject = new object[] { null, 0, 1 };
string empty = string.Empty;
try
{
empty = (string)this.typeX509Enrollment.InvokeMember("CreatePFX", BindingFlags.InvokeMethod, null, obj15, subject);
return new X509Certificate2(Convert.FromBase64String(empty), string.Empty, X509KeyStorageFlags.Exportable);
}
catch (Exception exception1)
{
Exception exception = exception1;
cert = null;
}
return cert;
}
private X509Certificate2 MakeCertificateInternal(string sSubjectCN, bool isRoot, bool switchToMTAIfNeeded,X509Certificate2 signingCert=null)
{
X509Certificate2 rCert=null;
if (switchToMTAIfNeeded && Thread.CurrentThread.GetApartmentState() != ApartmentState.MTA)
{
ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem((object o) =>
{
rCert = this.MakeCertificateInternal(sSubjectCN, isRoot, false,signingCert);
manualResetEvent.Set();
});
manualResetEvent.WaitOne();
manualResetEvent.Close();
return rCert;
}
string fullSubject = string.Format("CN={0}{1}", sSubjectCN, "");//Subject
string HashAlgo = "SHA256"; //Sig Algo
int GraceDays = -366; //Grace Days
int ValidDays = 1825; //ValiDays
int keyLength = 2048; //KeyLength
DateTime graceTime = DateTime.Now.AddDays((double)GraceDays);
DateTime now = DateTime.Now;
try
{
if (!isRoot)
{
rCert = this.MakeCertificate(false, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), signingCert);
}
else
{
rCert = this.MakeCertificate(true, sSubjectCN, fullSubject, keyLength, HashAlgo, graceTime, now.AddDays((double)ValidDays), null);
}
}
catch (Exception e)
{
throw e;
}
return rCert;
}
}
}
......@@ -5,15 +5,33 @@ using System.Threading.Tasks;
using System.Linq;
using System.Collections.Concurrent;
using System.IO;
using Titanium.Web.Proxy.Network.Certificate;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network
{
/// <summary>
/// Certificate Engine option
/// </summary>
public enum CertificateEngine
{
/// <summary>
/// Uses Windows Certification Generation API
/// </summary>
DefaultWindows = 0,
/// <summary>
/// Uses BouncyCastle 3rd party library
/// </summary>
BouncyCastle = 1
}
/// <summary>
/// A class to manage SSL certificates used by this proxy server
/// </summary>
internal class CertificateManager : IDisposable
{
private CertificateMaker certEngine = null;
private readonly ICertificateMaker certEngine;
private bool clearCertificates { get; set; }
/// <summary>
......@@ -21,18 +39,30 @@ namespace Titanium.Web.Proxy.Network
/// </summary>
private readonly IDictionary<string, CachedCertificate> certificateCache;
private Action<Exception> exceptionFunc;
private readonly Action<Exception> exceptionFunc;
internal string Issuer { get; private set; }
internal string RootCertificateName { get; private set; }
internal string Issuer { get; }
internal string RootCertificateName { get; }
internal X509Certificate2 rootCertificate { get; set; }
internal CertificateManager(string issuer, string rootCertificateName, Action<Exception> exceptionFunc)
internal CertificateManager(CertificateEngine engine,
string issuer,
string rootCertificateName,
Action<Exception> exceptionFunc)
{
this.exceptionFunc = exceptionFunc;
certEngine = new CertificateMaker();
//For Mono only Bouncy Castle is supported
if (RunTime.IsRunningOnMono()
|| engine == CertificateEngine.BouncyCastle)
{
certEngine = new BCCertificateMaker();
}
else
{
certEngine = new WinCertificateMaker();
}
Issuer = issuer;
RootCertificateName = rootCertificateName;
......@@ -42,10 +72,11 @@ namespace Titanium.Web.Proxy.Network
internal X509Certificate2 GetRootCertificate()
{
var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "rootCert.pfx");
var path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (null == path) throw new NullReferenceException();
var fileName = Path.Combine(path, "rootCert.pfx");
if (File.Exists(fileName))
{
if (!File.Exists(fileName)) return null;
try
{
return new X509Certificate2(fileName, string.Empty, X509KeyStorageFlags.Exportable);
......@@ -57,8 +88,6 @@ namespace Titanium.Web.Proxy.Network
return null;
}
}
return null;
}
/// <summary>
/// Attempts to create a RootCertificate
/// </summary>
......@@ -75,7 +104,7 @@ namespace Titanium.Web.Proxy.Network
{
rootCertificate = CreateCertificate(RootCertificateName, true);
}
catch(Exception e)
catch (Exception e)
{
exceptionFunc(e);
}
......@@ -83,38 +112,36 @@ namespace Titanium.Web.Proxy.Network
{
try
{
var fileName = Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "rootCert.pfx");
var path = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (null == path) throw new NullReferenceException();
var fileName = Path.Combine(path, "rootCert.pfx");
File.WriteAllBytes(fileName, rootCertificate.Export(X509ContentType.Pkcs12));
}
catch(Exception e)
catch (Exception e)
{
exceptionFunc(e);
}
}
return rootCertificate != null;
}
/// <summary>
/// Create an SSL certificate
/// </summary>
/// <param name="store"></param>
/// <param name="certificateName"></param>
/// <param name="isRootCertificate"></param>
/// <returns></returns>
internal virtual X509Certificate2 CreateCertificate(string certificateName, bool isRootCertificate)
{
try
{
if (certificateCache.ContainsKey(certificateName))
{
var cached = certificateCache[certificateName];
cached.LastAccess = DateTime.Now;
return cached.Certificate;
}
}
catch
{
}
X509Certificate2 certificate = null;
lock (string.Intern(certificateName))
{
......@@ -124,7 +151,7 @@ namespace Titanium.Web.Proxy.Network
{
certificate = certEngine.MakeCertificate(certificateName, isRootCertificate, rootCertificate);
}
catch(Exception e)
catch (Exception e)
{
exceptionFunc(e);
}
......@@ -165,9 +192,6 @@ namespace Titanium.Web.Proxy.Network
{
clearCertificates = true;
while (clearCertificates)
{
try
{
var cutOff = DateTime.Now.AddMinutes(-1 * certificateCacheTimeOutMinutes);
......@@ -177,46 +201,52 @@ namespace Titanium.Web.Proxy.Network
foreach (var cache in outdated)
certificateCache.Remove(cache.Key);
}
finally
{
}
//after a minute come back to check for outdated certificates in cache
await Task.Delay(1000 * 60);
}
}
internal bool TrustRootCertificate()
/// <summary>
/// Make current machine trust the Root Certificate used by this proxy
/// </summary>
/// <param name="storeLocation"></param>
/// <param name="exceptionFunc"></param>
/// <returns></returns>
internal void TrustRootCertificate(StoreLocation storeLocation,
Action<Exception> exceptionFunc)
{
if (rootCertificate == null)
{
return false;
exceptionFunc(
new Exception("Could not set root certificate"
+ " as system proxy since it is null or empty."));
return;
}
X509Store x509RootStore = new X509Store(StoreName.Root, storeLocation);
var x509PersonalStore = new X509Store(StoreName.My, storeLocation);
try
{
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
{
x509RootStore.Add(rootCertificate);
x509PersonalStore.Add(rootCertificate);
}
catch (Exception e)
{
exceptionFunc(
new Exception("Failed to make system trust root certificate "
+ $" for {storeLocation} store location. You may need admin rights.", e));
}
finally
{
x509RootStore.Close();
x509PersonalStore.Close();
}
return true;
}
catch
{
return false;
}
}
public void Dispose()
......
......@@ -47,6 +47,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
LastAccess = DateTime.Now;
}
/// <summary>
/// Dispose.
/// </summary>
public void Dispose()
{
Stream.Close();
......
......@@ -19,14 +19,12 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// </summary>
internal class TcpConnectionFactory
{
/// <summary>
/// Creates a TCP connection to server
/// </summary>
/// <param name="bufferSize"></param>
/// <param name="connectionTimeOutSeconds"></param>
/// <param name="remoteHostName"></param>
/// <param name="httpCmd"></param>
/// <param name="httpVersion"></param>
/// <param name="isHttps"></param>
/// <param name="remotePort"></param>
......@@ -57,7 +55,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
client = new TcpClient();
client.Client.Bind(upStreamEndPoint);
client.Client.Connect(externalHttpsProxy.HostName, externalHttpsProxy.Port);
await client.ConnectAsync(externalHttpsProxy.HostName, externalHttpsProxy.Port);
stream = client.GetStream();
using (var writer = new StreamWriter(stream, Encoding.ASCII, bufferSize, true) { NewLine = ProxyConstants.NewLine })
......@@ -69,7 +67,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (!string.IsNullOrEmpty(externalHttpsProxy.UserName) && externalHttpsProxy.Password != null)
{
await writer.WriteLineAsync("Proxy-Connection: keep-alive");
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(externalHttpsProxy.UserName + ":" + externalHttpsProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
......@@ -81,7 +79,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
var result = await reader.ReadLineAsync();
if (!new string[] { "200 OK", "connection established" }.Any(s => result.ToLower().Contains(s.ToLower())))
if (!new[] { "200 OK", "connection established" }.Any(s => result.ToLower().Contains(s.ToLower())))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
}
......@@ -93,7 +91,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
client = new TcpClient();
client.Client.Bind(upStreamEndPoint);
client.Client.Connect(remoteHostName, remotePort);
await client.ConnectAsync(remoteHostName, remotePort);
stream = client.GetStream();
}
......@@ -119,14 +117,14 @@ namespace Titanium.Web.Proxy.Network.Tcp
{
client = new TcpClient();
client.Client.Bind(upStreamEndPoint);
client.Client.Connect(externalHttpProxy.HostName, externalHttpProxy.Port);
await client.ConnectAsync(externalHttpProxy.HostName, externalHttpProxy.Port);
stream = client.GetStream();
}
else
{
client = new TcpClient();
client.Client.Bind(upStreamEndPoint);
client.Client.Connect(remoteHostName, remotePort);
await client.ConnectAsync(remoteHostName, remotePort);
stream = client.GetStream();
}
}
......
......@@ -29,16 +29,16 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary>
/// Gets the local end point.
/// </summary>
public IPEndPoint LocalEndPoint { get; private set; }
public IPEndPoint LocalEndPoint { get; }
/// <summary>
/// Gets the remote end point.
/// </summary>
public IPEndPoint RemoteEndPoint { get; private set; }
public IPEndPoint RemoteEndPoint { get; }
/// <summary>
/// Gets the process identifier.
/// </summary>
public int ProcessId { get; private set; }
public int ProcessId { get; }
}
}
\ No newline at end of file
......@@ -6,7 +6,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary>
/// Represents collection of TcpRows
/// </summary>
/// <seealso cref="System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}" />
/// <seealso>
/// <cref>System.Collections.Generic.IEnumerable{Proxy.Tcp.TcpRow}</cref>
/// </seealso>
internal class TcpTable : IEnumerable<TcpRow>
{
private readonly IEnumerable<TcpRow> tcpRows;
......
......@@ -24,32 +24,46 @@ namespace Titanium.Web.Proxy
try
{
if (!httpHeaders.Where(t => t.Name == "Proxy-Authorization").Any())
if (httpHeaders.All(t => t.Name != "Proxy-Authorization"))
{
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"));
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
return false;
}
else
{
var headerValue = httpHeaders.Where(t => t.Name == "Proxy-Authorization").FirstOrDefault().Value.Trim();
var header = httpHeaders.FirstOrDefault(t => t.Name == "Proxy-Authorization");
if (null == header) throw new NullReferenceException();
var headerValue = header.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"));
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
......@@ -63,10 +77,17 @@ namespace Titanium.Web.Proxy
//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"));
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{
"Proxy-Authenticate",
new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")
},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
......@@ -76,17 +97,20 @@ namespace Titanium.Web.Proxy
var password = decoded.Substring(decoded.IndexOf(':') + 1);
return await AuthenticateUserFunc(username, password).ConfigureAwait(false);
}
}
catch (Exception e)
{
ExceptionFunc(new ProxyAuthorizationException("Error whilst authorizing request", e, httpHeaders));
//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"));
var response = new Response
{
ResponseHeaders = new Dictionary<string, HttpHeader>
{
{"Proxy-Authenticate", new HttpHeader("Proxy-Authenticate", "Basic realm=\"TitaniumProxy\"")},
{"Proxy-Connection", new HttpHeader("Proxy-Connection", "close")}
}
};
await WriteResponseHeaders(clientStreamWriter, response);
await clientStreamWriter.WriteLineAsync();
......
......@@ -10,6 +10,7 @@ using Titanium.Web.Proxy.Network;
using System.Linq;
using System.Security.Authentication;
using Titanium.Web.Proxy.Network.Tcp;
using System.Security.Cryptography.X509Certificates;
namespace Titanium.Web.Proxy
{
......@@ -47,14 +48,20 @@ namespace Titanium.Web.Proxy
/// <summary>
/// A object that creates tcp connection to server
/// </summary>
private TcpConnectionFactory tcpConnectionFactory { get; set; }
private TcpConnectionFactory tcpConnectionFactory { get; }
/// <summary>
/// Manage system proxy settings
/// </summary>
private SystemProxyManager systemProxySettingsManager { get; set; }
private SystemProxyManager systemProxySettingsManager { get; }
private FireFoxProxySettingsManager firefoxProxySettingsManager { get; set; }
#if !DEBUG
/// <summary>
/// Set firefox to use default system proxy
/// </summary>
private FireFoxProxySettingsManager firefoxProxySettingsManager
= new FireFoxProxySettingsManager();
#endif
/// <summary>
/// Buffer size used throughout this proxy
......@@ -81,6 +88,13 @@ namespace Titanium.Web.Proxy
/// </summary>
public bool TrustRootCertificate { get; set; }
/// <summary>
/// Select Certificate Engine
/// Optionally set to BouncyCastle
/// Mono only support BouncyCastle and it is the default
/// </summary>
public CertificateEngine CertificateEngine { 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
......@@ -123,6 +137,16 @@ namespace Titanium.Web.Proxy
/// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0);
/// <summary>
/// Is the proxy currently running
/// </summary>
public bool ProxyRunning => proxyRunning;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// Verifies the remote Secure Sockets Layer (SSL) certificate used for authentication
/// </summary>
......@@ -138,14 +162,8 @@ namespace Titanium.Web.Proxy
/// </summary>
public Action<Exception> ExceptionFunc
{
get
{
return exceptionFunc ?? defaultExceptionFunc.Value;
}
set
{
exceptionFunc = value;
}
get { return exceptionFunc ?? defaultExceptionFunc.Value; }
set { exceptionFunc = value; }
}
/// <summary>
......@@ -172,6 +190,7 @@ namespace Titanium.Web.Proxy
/// <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
/// </summary>
public Func<SessionEventArgs, Task<ExternalProxy>> GetCustomUpStreamHttpsProxyFunc
{
get;
......@@ -188,25 +207,21 @@ namespace Titanium.Web.Proxy
/// </summary>
public SslProtocols SupportedSslProtocols { get; set; } = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3;
/// <summary>
/// Is the proxy currently running
/// </summary>
public bool ProxyRunning => proxyRunning;
/// <summary>
/// Gets or sets a value indicating whether requests will be chained to upstream gateway.
/// </summary>
public bool ForwardToUpstreamGateway { get; set; }
/// <summary>
/// Constructor
/// </summary>
public ProxyServer() : this(null, null) { }
/// <summary>
/// Constructor.
/// </summary>
/// <param name="rootCertificateName">Name of root certificate.</param>
/// <param name="rootCertificateIssuerName">Name of root certificate issuer.</param>
public ProxyServer(string rootCertificateName, string rootCertificateIssuerName)
{
RootCertificateName = rootCertificateName;
RootCertificateIssuerName = rootCertificateIssuerName;
//default values
ConnectionTimeOutSeconds = 120;
CertificateCacheTimeOutMinutes = 60;
......@@ -214,8 +229,9 @@ namespace Titanium.Web.Proxy
ProxyEndPoints = new List<ProxyEndPoint>();
tcpConnectionFactory = new TcpConnectionFactory();
systemProxySettingsManager = new SystemProxyManager();
firefoxProxySettingsManager = new FireFoxProxySettingsManager();
#if !DEBUG
new FireFoxProxySettingsManager();
#endif
RootCertificateName = RootCertificateName ?? "Titanium Root Certificate Authority";
RootCertificateIssuerName = RootCertificateIssuerName ?? "Titanium";
}
......@@ -277,7 +293,7 @@ namespace Titanium.Web.Proxy
#if !DEBUG
firefoxProxySettingsManager.AddFirefox();
#endif
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTP Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTP Proxy", endPoint.IpAddress, endPoint.Port);
}
......@@ -313,7 +329,7 @@ namespace Titanium.Web.Proxy
#if !DEBUG
firefoxProxySettingsManager.AddFirefox();
#endif
Console.WriteLine("Set endpoint at Ip {1} and port: {2} as System HTTPS Proxy", endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
Console.WriteLine("Set endpoint at Ip {0} and port: {1} as System HTTPS Proxy", endPoint.IpAddress, endPoint.Port);
}
/// <summary>
......@@ -350,14 +366,21 @@ namespace Titanium.Web.Proxy
throw new Exception("Proxy is already running.");
}
certificateCacheManager = new CertificateManager(RootCertificateIssuerName,
certificateCacheManager = new CertificateManager(CertificateEngine,
RootCertificateIssuerName,
RootCertificateName, ExceptionFunc);
certValidated = certificateCacheManager.CreateTrustedRootCertificate();
if (TrustRootCertificate)
{
certificateCacheManager.TrustRootCertificate();
//current user
certificateCacheManager
.TrustRootCertificate(StoreLocation.CurrentUser, exceptionFunc);
//current system
certificateCacheManager
.TrustRootCertificate(StoreLocation.LocalMachine, exceptionFunc);
}
if (ForwardToUpstreamGateway && GetCustomUpStreamHttpProxyFunc == null && GetCustomUpStreamHttpsProxyFunc == null)
......@@ -461,6 +484,7 @@ namespace Titanium.Web.Proxy
/// <param name="endPoint"></param>
private void ValidateEndPointAsSystemProxy(ExplicitProxyEndPoint endPoint)
{
if (endPoint == null) throw new ArgumentNullException(nameof(endPoint));
if (ProxyEndPoints.Contains(endPoint) == false)
{
throw new Exception("Cannot set endPoints not added to proxy as system proxy");
......@@ -542,6 +566,9 @@ namespace Titanium.Web.Proxy
endPoint.listener.BeginAcceptTcpClient(OnAcceptConnection, endPoint);
}
/// <summary>
/// Dispose Proxy.
/// </summary>
public void Dispose()
{
if (proxyRunning)
......@@ -551,5 +578,45 @@ namespace Titanium.Web.Proxy
certificateCacheManager?.Dispose();
}
/// <summary>
/// Invocator for BeforeRequest event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnBeforeRequest(object sender, SessionEventArgs e)
{
BeforeRequest?.Invoke(sender, e);
}
/// <summary>
/// Invocator for BeforeResponse event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <returns></returns>
protected virtual void OnBeforeResponse(object sender, SessionEventArgs e)
{
BeforeResponse?.Invoke(sender, e);
}
/// <summary>
/// Invocator for ServerCertificateValidationCallback event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnServerCertificateValidationCallback(object sender, CertificateValidationEventArgs e)
{
ServerCertificateValidationCallback?.Invoke(sender, e);
}
/// <summary>
/// Invocator for ClientCertifcateSelectionCallback event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void OnClientCertificateSelectionCallback(object sender, CertificateSelectionEventArgs e)
{
ClientCertificateSelectionCallback?.Invoke(sender, e);
}
}
}
\ No newline at end of file
......@@ -11,7 +11,6 @@ using Titanium.Web.Proxy.Exceptions;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models;
using System.Security.Cryptography.X509Certificates;
using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Http;
using System.Threading.Tasks;
......@@ -31,7 +30,6 @@ namespace Titanium.Web.Proxy
private async Task HandleClient(ExplicitProxyEndPoint endPoint, TcpClient tcpClient)
{
Stream clientStream = tcpClient.GetStream();
clientStream.ReadTimeout = ConnectionTimeOutSeconds * 1000;
......@@ -58,30 +56,24 @@ namespace Titanium.Web.Proxy
//Find the request Verb
var httpVerb = httpCmdSplit[0];
if (httpVerb.ToUpper() == "CONNECT")
{
httpRemoteUri = new Uri("http://" + httpCmdSplit[1]);
}
else
{
httpRemoteUri = new Uri(httpCmdSplit[1]);
}
httpRemoteUri = httpVerb.ToUpper() == "CONNECT" ?
new Uri("http://" + httpCmdSplit[1]) : new Uri(httpCmdSplit[1]);
//parse the HTTP version
Version version = new Version(1, 1);
var version = new Version(1, 1);
if (httpCmdSplit.Length == 3)
{
string httpVersion = httpCmdSplit[2].Trim();
var httpVersion = httpCmdSplit[2].Trim();
if (httpVersion == "http/1.0")
if (0 == string.CompareOrdinal(httpVersion, "http/1.0"))
{
version = new Version(1, 0);
}
}
//filter out excluded host names
var excluded = endPoint.ExcludedHttpsHostNameRegex != null ?
endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x)) : false;
//filter out excluded host names
var excluded = endPoint.ExcludedHttpsHostNameRegex != null
&& endPoint.ExcludedHttpsHostNameRegex.Any(x => Regex.IsMatch(httpRemoteUri.Host, x));
List<HttpHeader> connectRequestHeaders = null;
......@@ -89,7 +81,7 @@ namespace Titanium.Web.Proxy
if (httpVerb.ToUpper() == "CONNECT" && !excluded && httpRemoteUri.Port != 80)
{
httpRemoteUri = new Uri("https://" + httpCmdSplit[1]);
string tmpLine = null;
string tmpLine;
connectRequestHeaders = new List<HttpHeader>();
while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
{
......@@ -114,15 +106,7 @@ namespace Titanium.Web.Proxy
sslStream = new SslStream(clientStream, true);
X509Certificate2 certificate;
if (endPoint.GenericCertificate != null)
{
certificate = endPoint.GenericCertificate;
}
else
{
certificate = certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false);
}
var certificate = endPoint.GenericCertificate ?? certificateCacheManager.CreateCertificate(httpRemoteUri.Host, false);
//Successfully managed to authenticate the client using the fake certificate
await sslStream.AuthenticateAsServerAsync(certificate, false,
......@@ -136,10 +120,7 @@ namespace Titanium.Web.Proxy
}
catch
{
if (sslStream != null)
{
sslStream.Dispose();
}
sslStream?.Dispose();
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
return;
......@@ -160,8 +141,8 @@ namespace Titanium.Web.Proxy
await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, version, null,
false, SupportedSslProtocols,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate),
ValidateServerCertificate,
SelectClientCertificate,
clientStream, tcpConnectionFactory, UpStreamEndPoint);
Dispose(clientStream, clientStreamReader, clientStreamWriter, null);
......@@ -169,7 +150,7 @@ namespace Titanium.Web.Proxy
}
//Now create the request
await HandleHttpSessionRequest(tcpClient, httpCmd, clientStream, clientStreamReader, clientStreamWriter,
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequestHeaders, null, null);
httpRemoteUri.Scheme == Uri.UriSchemeHttps ? httpRemoteUri.Host : null, endPoint, connectRequestHeaders);
}
catch (Exception)
{
......@@ -189,14 +170,13 @@ namespace Titanium.Web.Proxy
CustomBinaryReader clientStreamReader = null;
StreamWriter clientStreamWriter = null;
X509Certificate2 certificate = null;
if (endPoint.EnableSsl)
{
var sslStream = new SslStream(clientStream, true);
//implement in future once SNI supported by SSL stream, for now use the same certificate
certificate = certificateCacheManager.CreateCertificate(endPoint.GenericCertificateName, false);
var certificate = certificateCacheManager.CreateCertificate(endPoint.GenericCertificateName, false);
try
{
......@@ -259,8 +239,8 @@ namespace Titanium.Web.Proxy
connection = await tcpConnectionFactory.CreateClient(BUFFER_SIZE, ConnectionTimeOutSeconds,
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.WebSession.Request.HttpVersion,
args.IsHttps, SupportedSslProtocols,
new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate),
ValidateServerCertificate,
SelectClientCertificate,
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpsProxy ?? UpStreamHttpsProxy, args.ProxyClient.ClientStream, UpStreamEndPoint);
}
......@@ -367,9 +347,14 @@ namespace Titanium.Web.Proxy
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
/// <param name="httpsHostName"></param>
/// <param name="endPoint"></param>
/// <param name="connectHeaders"></param>
/// <param name="customUpStreamHttpProxy"></param>
/// <param name="customUpStreamHttpsProxy"></param>
/// <returns></returns>
private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ProxyEndPoint endPoint, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName,
ProxyEndPoint endPoint, List<HttpHeader> connectHeaders, ExternalProxy customUpStreamHttpProxy = null, ExternalProxy customUpStreamHttpsProxy = null)
{
TcpConnection connection = null;
......@@ -383,9 +368,12 @@ namespace Titanium.Web.Proxy
break;
}
var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
args.ProxyClient.TcpClient = client;
args.WebSession.ConnectHeaders = connectHeaders;
var args =
new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse)
{
ProxyClient = {TcpClient = client},
WebSession = {ConnectHeaders = connectHeaders}
};
args.WebSession.ProcessId = new Lazy<int>(() =>
{
......@@ -409,12 +397,12 @@ namespace Titanium.Web.Proxy
var httpMethod = httpCmdSplit[0];
//find the request HTTP version
Version httpVersion = new Version(1, 1);
var httpVersion = new Version(1, 1);
if (httpCmdSplit.Length == 3)
{
var httpVersionString = httpCmdSplit[2].ToLower().Trim();
if (httpVersionString == "http/1.0")
if (0 == string.CompareOrdinal(httpVersionString, "http/1.0"))
{
httpVersion = new Version(1, 0);
}
......@@ -439,10 +427,8 @@ namespace Titanium.Web.Proxy
{
var existing = args.WebSession.Request.RequestHeaders[newHeader.Name];
var nonUniqueHeaders = new List<HttpHeader>();
var nonUniqueHeaders = new List<HttpHeader> {existing, newHeader};
nonUniqueHeaders.Add(existing);
nonUniqueHeaders.Add(newHeader);
args.WebSession.Request.NonUniqueRequestHeaders.Add(newHeader.Name, nonUniqueHeaders);
args.WebSession.Request.RequestHeaders.Remove(newHeader.Name);
......@@ -455,8 +441,7 @@ namespace Titanium.Web.Proxy
}
var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1]
: (string.Concat("https://", args.WebSession.Request.Host == null ?
httpsHostName : args.WebSession.Request.Host, httpCmdSplit[1])));
: (string.Concat("https://", args.WebSession.Request.Host ?? httpsHostName, httpCmdSplit[1])));
args.WebSession.Request.RequestUri = httpRemoteUri;
......@@ -479,10 +464,10 @@ namespace Titanium.Web.Proxy
//If user requested interception do it
if (BeforeRequest != null)
{
Delegate[] invocationList = BeforeRequest.GetInvocationList();
Task[] handlerTasks = new Task[invocationList.Length];
var invocationList = BeforeRequest.GetInvocationList();
var handlerTasks = new Task[invocationList.Length];
for (int i = 0; i < invocationList.Length; i++)
for (var i = 0; i < invocationList.Length; i++)
{
handlerTasks[i] = ((Func<object, SessionEventArgs, Task>)invocationList[i])(null, args);
}
......@@ -495,8 +480,8 @@ namespace Titanium.Web.Proxy
{
await TcpHelper.SendRaw(BUFFER_SIZE, ConnectionTimeOutSeconds, httpRemoteUri.Host, httpRemoteUri.Port,
httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps,
SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
new LocalCertificateSelectionCallback(SelectClientCertificate),
SupportedSslProtocols, ValidateServerCertificate,
SelectClientCertificate,
clientStream, tcpConnectionFactory, UpStreamEndPoint);
Dispose(clientStream, clientStreamReader, clientStreamWriter, args);
......@@ -504,7 +489,7 @@ namespace Titanium.Web.Proxy
}
//construct the web request that we are going to issue on behalf of the client.
await HandleHttpSessionRequestInternal(connection, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false).ConfigureAwait(false);
await HandleHttpSessionRequestInternal(null, args, customUpStreamHttpProxy, customUpStreamHttpsProxy, false).ConfigureAwait(false);
if (args.WebSession.Request.CancelRequest)
......@@ -531,12 +516,8 @@ namespace Titanium.Web.Proxy
}
if (connection != null)
{
//dispose
connection.Dispose();
}
connection?.Dispose();
}
/// <summary>
......@@ -547,8 +528,9 @@ namespace Titanium.Web.Proxy
/// <returns></returns>
private async Task WriteConnectResponse(StreamWriter clientStreamWriter, Version httpVersion)
{
await clientStreamWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2}", httpVersion.Major, httpVersion.Minor, "200 Connection established"));
await clientStreamWriter.WriteLineAsync(string.Format("Timestamp: {0}", DateTime.Now));
await clientStreamWriter.WriteLineAsync(
$"HTTP/{httpVersion.Major}.{httpVersion.Minor} 200 Connection established");
await clientStreamWriter.WriteLineAsync($"Timestamp: {DateTime.Now}");
await clientStreamWriter.WriteLineAsync();
await clientStreamWriter.FlushAsync();
}
......@@ -568,10 +550,7 @@ namespace Titanium.Web.Proxy
{
//these are the only encoding this proxy can read
case "accept-encoding":
header.Value = "gzip,deflate,zlib";
break;
default:
header.Value = "gzip,deflate";
break;
}
}
......
......@@ -17,7 +17,11 @@ namespace Titanium.Web.Proxy
/// </summary>
partial class ProxyServer
{
//Called asynchronously when a request was successfully and we received the response
/// <summary>
/// Called asynchronously when a request was successfully and we received the response
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
public async Task HandleHttpSessionResponse(SessionEventArgs args)
{
//read response & headers from server
......@@ -156,14 +160,14 @@ namespace Titanium.Web.Proxy
private async Task WriteResponseStatus(Version version, string code, string description,
StreamWriter responseWriter)
{
await responseWriter.WriteLineAsync(string.Format("HTTP/{0}.{1} {2} {3}", version.Major, version.Minor, code, description));
await responseWriter.WriteLineAsync($"HTTP/{version.Major}.{version.Minor} {code} {description}");
}
/// <summary>
/// Write response headers to client
/// </summary>
/// <param name="responseWriter"></param>
/// <param name="headers"></param>
/// <param name="response"></param>
/// <returns></returns>
private async Task WriteResponseHeaders(StreamWriter responseWriter, Response response)
{
......@@ -220,7 +224,6 @@ namespace Titanium.Web.Proxy
/// <summary>
/// Handle dispose of a client/server session
/// </summary>
/// <param name="tcpClient"></param>
/// <param name="clientStream"></param>
/// <param name="clientStreamReader"></param>
/// <param name="clientStreamWriter"></param>
......@@ -235,21 +238,13 @@ namespace Titanium.Web.Proxy
clientStream.Dispose();
}
if (args != null)
{
args.Dispose();
}
args?.Dispose();
if (clientStreamReader != null)
{
clientStreamReader.Dispose();
}
clientStreamReader?.Dispose();
if (clientStreamWriter != null)
{
if (clientStreamWriter == null) return;
clientStreamWriter.Close();
clientStreamWriter.Dispose();
}
}
}
}
\ No newline at end of file
using System;
using System.Text;
using System.Text;
namespace Titanium.Web.Proxy.Shared
{
......
......@@ -34,6 +34,8 @@
<DefineConstants>NET45</DefineConstants>
<Prefer32Bit>false</Prefer32Bit>
<DocumentationFile>bin\Release\Titanium.Web.Proxy.XML</DocumentationFile>
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
......@@ -42,8 +44,8 @@
<AssemblyOriginatorKeyFile>StrongNameKey.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Ionic.Zip, Version=1.9.8.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL">
<HintPath>..\packages\DotNetZip.1.9.8\lib\net20\Ionic.Zip.dll</HintPath>
<Reference Include="BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
<HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
......@@ -62,20 +64,21 @@
<Compile Include="Compression\DeflateCompression.cs" />
<Compile Include="Compression\GZipCompression.cs" />
<Compile Include="Compression\ICompression.cs" />
<Compile Include="Compression\ZlibCompression.cs" />
<Compile Include="Decompression\DecompressionFactory.cs" />
<Compile Include="Decompression\DefaultDecompression.cs" />
<Compile Include="Decompression\DeflateDecompression.cs" />
<Compile Include="Decompression\GZipDecompression.cs" />
<Compile Include="Decompression\IDecompression.cs" />
<Compile Include="Decompression\ZlibDecompression.cs" />
<Compile Include="EventArguments\CertificateSelectionEventArgs.cs" />
<Compile Include="EventArguments\CertificateValidationEventArgs.cs" />
<Compile Include="Extensions\ByteArrayExtensions.cs" />
<Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\RunTime.cs" />
<Compile Include="Http\Responses\GenericResponse.cs" />
<Compile Include="Network\CachedCertificate.cs" />
<Compile Include="Network\CertificateMaker.cs" />
<Compile Include="Network\Certificate\WinCertificateMaker.cs" />
<Compile Include="Network\Certificate\BCCertificateMaker.cs" />
<Compile Include="Network\Certificate\ICertificateMaker.cs" />
<Compile Include="Network\ProxyClient.cs" />
<Compile Include="Exceptions\BodyNotFoundException.cs" />
<Compile Include="Exceptions\ProxyAuthorizationException.cs" />
......
......@@ -14,7 +14,7 @@
<copyright>Copyright &#x00A9; Titanium. All rights reserved.</copyright>
<tags></tags>
<dependencies>
<dependency id="DotNetZip" version="1.9.8" />
<dependency id="BouncyCastle" version="1.8.1" />
</dependencies>
</metadata>
<files>
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DotNetZip" version="1.9.8" targetFramework="net45" />
<package id="BouncyCastle" version="1.8.1" targetFramework="net45" />
</packages>
\ 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