Unverified Commit 4a7ccfac authored by honfika's avatar honfika Committed by GitHub

Merge pull request #625 from justcoding121/beta

Stable
parents 07377916 53ba585d
## Titanium Web Proxy ## Titanium Web Proxy
### Note: This Project is no longer maintained. Any pull requests for fixes are welcome.
A lightweight HTTP(S) proxy server written in C#. A lightweight HTTP(S) proxy server written in C#.
<a href="https://ci.appveyor.com/project/justcoding121/titanium-web-proxy">![Build Status](https://ci.appveyor.com/api/projects/status/p5vvtbpx9yp250ol?svg=true)</a> [![Join the chat at https://gitter.im/Titanium-Web-Proxy/Lobby](https://badges.gitter.im/Titanium-Web-Proxy/Lobby.svg)](https://gitter.im/Titanium-Web-Proxy/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) <a href="https://ci.appveyor.com/project/justcoding121/titanium-web-proxy">![Build Status](https://ci.appveyor.com/api/projects/status/p5vvtbpx9yp250ol?svg=true)</a> [![Join the chat at https://gitter.im/Titanium-Web-Proxy/Lobby](https://badges.gitter.im/Titanium-Web-Proxy/Lobby.svg)](https://gitter.im/Titanium-Web-Proxy/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
...@@ -32,7 +30,7 @@ For stable releases on [stable branch](https://github.com/justcoding121/Titanium ...@@ -32,7 +30,7 @@ For stable releases on [stable branch](https://github.com/justcoding121/Titanium
Supports Supports
* .Net Standard 2.0 or above * .Net Standard 2.0 or above
* .Net Framework 4.5 or above * .Net Framework 4.6.1 or above
### Development environment ### Development environment
...@@ -57,11 +55,11 @@ Setup HTTP proxy: ...@@ -57,11 +55,11 @@ Setup HTTP proxy:
```csharp ```csharp
var proxyServer = new ProxyServer(); var proxyServer = new ProxyServer();
//locally trust root certificate used by this proxy // locally trust root certificate used by this proxy
proxyServer.CertificateManager.TrustRootCertificate = true; proxyServer.CertificateManager.TrustRootCertificate = true;
//optionally set the Certificate Engine // optionally set the Certificate Engine
//Under Mono only BouncyCastle will be supported // Under Mono only BouncyCastle will be supported
//proxyServer.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle; //proxyServer.CertificateManager.CertificateEngine = Network.CertificateEngine.BouncyCastle;
proxyServer.BeforeRequest += OnRequest; proxyServer.BeforeRequest += OnRequest;
...@@ -72,27 +70,27 @@ proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection; ...@@ -72,27 +70,27 @@ proxyServer.ClientCertificateSelectionCallback += OnCertificateSelection;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true) var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{ {
//Use self-issued generic certificate on all https requests // Use self-issued generic certificate on all https requests
//Optimizes performance by not creating a certificate for each https-enabled domain // Optimizes performance by not creating a certificate for each https-enabled domain
//Useful when certificate trust is not required 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") //GenericCertificate = new X509Certificate2(Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "genericcert.pfx"), "password")
}; };
//Fired when a CONNECT request is received // Fired when a CONNECT request is received
explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect; explicitEndPoint.BeforeTunnelConnect += OnBeforeTunnelConnect;
//An explicit endpoint is where the client knows about the existence 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 // So client sends request in a proxy friendly manner
proxyServer.AddEndPoint(explicitEndPoint); proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.Start(); proxyServer.Start();
//Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy) // Transparent endpoint is useful for reverse proxy (client is not aware of the existence of proxy)
//A transparent endpoint usually requires a network router port forwarding HTTP(S) packets or DNS // A transparent endpoint usually requires a network router port forwarding HTTP(S) packets or DNS
//to send data to this endPoint // to send data to this endPoint
var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true) var transparentEndPoint = new TransparentProxyEndPoint(IPAddress.Any, 8001, true)
{ {
//Generic Certificate hostname to use // Generic Certificate hostname to use
//when SNI is disabled by client // when SNI is disabled by client
GenericCertificateName = "google.com" GenericCertificateName = "google.com"
}; };
...@@ -105,14 +103,14 @@ foreach (var endPoint in proxyServer.ProxyEndPoints) ...@@ -105,14 +103,14 @@ foreach (var endPoint in proxyServer.ProxyEndPoints)
Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ", Console.WriteLine("Listening on '{0}' endpoint at Ip {1} and port: {2} ",
endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port); endPoint.GetType().Name, endPoint.IpAddress, endPoint.Port);
//Only explicit proxies can be set as system proxy! // Only explicit proxies can be set as system proxy!
proxyServer.SetAsSystemHttpProxy(explicitEndPoint); proxyServer.SetAsSystemHttpProxy(explicitEndPoint);
proxyServer.SetAsSystemHttpsProxy(explicitEndPoint); proxyServer.SetAsSystemHttpsProxy(explicitEndPoint);
//wait here (You can use something else as a wait function, I am using this as a demo) // wait here (You can use something else as a wait function, I am using this as a demo)
Console.Read(); Console.Read();
//Unsubscribe & Quit // Unsubscribe & Quit
explicitEndPoint.BeforeTunnelConnect -= OnBeforeTunnelConnect; explicitEndPoint.BeforeTunnelConnect -= OnBeforeTunnelConnect;
proxyServer.BeforeRequest -= OnRequest; proxyServer.BeforeRequest -= OnRequest;
proxyServer.BeforeResponse -= OnResponse; proxyServer.BeforeResponse -= OnResponse;
...@@ -132,9 +130,9 @@ private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSess ...@@ -132,9 +130,9 @@ private async Task OnBeforeTunnelConnectRequest(object sender, TunnelConnectSess
if (hostname.Contains("dropbox.com")) if (hostname.Contains("dropbox.com"))
{ {
//Exclude Https addresses you don't want to proxy // Exclude Https addresses you don't want to proxy
//Useful for clients that use certificate pinning // Useful for clients that use certificate pinning
//for example dropbox.com // for example dropbox.com
e.DecryptSsl = false; e.DecryptSsl = false;
} }
} }
...@@ -143,27 +141,27 @@ public async Task OnRequest(object sender, SessionEventArgs e) ...@@ -143,27 +141,27 @@ public async Task OnRequest(object sender, SessionEventArgs e)
{ {
Console.WriteLine(e.HttpClient.Request.Url); Console.WriteLine(e.HttpClient.Request.Url);
////read request headers // read request headers
var requestHeaders = e.HttpClient.Request.RequestHeaders; var requestHeaders = e.HttpClient.Request.RequestHeaders;
var method = e.HttpClient.Request.Method.ToUpper(); var method = e.HttpClient.Request.Method.ToUpper();
if ((method == "POST" || method == "PUT" || method == "PATCH")) if ((method == "POST" || method == "PUT" || method == "PATCH"))
{ {
//Get/Set request body bytes // Get/Set request body bytes
byte[] bodyBytes = await e.GetRequestBody(); byte[] bodyBytes = await e.GetRequestBody();
await e.SetRequestBody(bodyBytes); await e.SetRequestBody(bodyBytes);
//Get/Set request body as string // Get/Set request body as string
string bodyString = await e.GetRequestBodyAsString(); string bodyString = await e.GetRequestBodyAsString();
await e.SetRequestBodyString(bodyString); await e.SetRequestBodyString(bodyString);
//store request // store request
//so that you can find it from response handler // so that you can find it from response handler
e.UserData = e.HttpClient.Request; e.UserData = e.HttpClient.Request;
} }
//To cancel a request with a custom HTML content // To cancel a request with a custom HTML content
//Filter URL // Filter URL
if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("google.com")) if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("google.com"))
{ {
e.Ok("<!DOCTYPE html>" + e.Ok("<!DOCTYPE html>" +
...@@ -174,17 +172,18 @@ public async Task OnRequest(object sender, SessionEventArgs e) ...@@ -174,17 +172,18 @@ public async Task OnRequest(object sender, SessionEventArgs e)
"</body>" + "</body>" +
"</html>"); "</html>");
} }
//Redirect example
// Redirect example
if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org")) if (e.HttpClient.Request.RequestUri.AbsoluteUri.Contains("wikipedia.org"))
{ {
e.Redirect("https://www.paypal.com"); e.Redirect("https://www.paypal.com");
} }
} }
//Modify response // Modify response
public async Task OnResponse(object sender, SessionEventArgs e) public async Task OnResponse(object sender, SessionEventArgs e)
{ {
//read response headers // read response headers
var responseHeaders = e.HttpClient.Response.ResponseHeaders; var responseHeaders = e.HttpClient.Response.ResponseHeaders;
//if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return; //if (!e.ProxySession.Request.Host.Equals("medeczane.sgk.gov.tr")) return;
...@@ -203,28 +202,27 @@ public async Task OnResponse(object sender, SessionEventArgs e) ...@@ -203,28 +202,27 @@ public async Task OnResponse(object sender, SessionEventArgs e)
} }
} }
if(e.UserData!=null) if (e.UserData!=null)
{ {
//access request from UserData property where we stored it in RequestHandler // access request from UserData property where we stored it in RequestHandler
var request = (Request)e.UserData; var request = (Request)e.UserData;
} }
} }
/// Allows overriding default certificate validation logic // Allows overriding default certificate validation logic
public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e) public Task OnCertificateValidation(object sender, CertificateValidationEventArgs e)
{ {
//set IsValid to true/false based on Certificate Errors // set IsValid to true/false based on Certificate Errors
if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None) if (e.SslPolicyErrors == System.Net.Security.SslPolicyErrors.None)
e.IsValid = true; e.IsValid = true;
return Task.FromResult(0); return Task.FromResult(0);
} }
/// Allows overriding default client certificate selection logic during mutual authentication // Allows overriding default client certificate selection logic during mutual authentication
public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e) public Task OnCertificateSelection(object sender, CertificateSelectionEventArgs e)
{ {
//set e.clientCertificate to override // set e.clientCertificate to override
return Task.FromResult(0); return Task.FromResult(0);
} }
``` ```
......
...@@ -119,7 +119,7 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -119,7 +119,7 @@ namespace Titanium.Web.Proxy.Examples.Basic
private async Task onBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e) private async Task onBeforeTunnelConnectRequest(object sender, TunnelConnectSessionEventArgs e)
{ {
string hostname = e.HttpClient.Request.RequestUri.Host; string hostname = e.HttpClient.Request.RequestUri.Host;
//await writeToConsole("Tunnel to: " + hostname); await writeToConsole("Tunnel to: " + hostname);
if (hostname.Contains("dropbox.com")) if (hostname.Contains("dropbox.com"))
{ {
...@@ -138,8 +138,8 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -138,8 +138,8 @@ namespace Titanium.Web.Proxy.Examples.Basic
// intecept & cancel redirect or update requests // intecept & cancel redirect or update requests
private async Task onRequest(object sender, SessionEventArgs e) private async Task onRequest(object sender, SessionEventArgs e)
{ {
//await writeToConsole("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount); await writeToConsole("Active Client Connections:" + ((ProxyServer)sender).ClientConnectionCount);
//await writeToConsole(e.HttpClient.Request.Url); await writeToConsole(e.HttpClient.Request.Url);
// store it in the UserData property // store it in the UserData property
// It can be a simple integer, Guid, or any type // It can be a simple integer, Guid, or any type
...@@ -189,11 +189,11 @@ namespace Titanium.Web.Proxy.Examples.Basic ...@@ -189,11 +189,11 @@ namespace Titanium.Web.Proxy.Examples.Basic
private async Task onResponse(object sender, SessionEventArgs e) private async Task onResponse(object sender, SessionEventArgs e)
{ {
//await writeToConsole("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount); await writeToConsole("Active Server Connections:" + ((ProxyServer)sender).ServerConnectionCount);
string ext = System.IO.Path.GetExtension(e.HttpClient.Request.RequestUri.AbsolutePath); string ext = System.IO.Path.GetExtension(e.HttpClient.Request.RequestUri.AbsolutePath);
//access user data set in request to do something with it // access user data set in request to do something with it
//var userData = e.HttpClient.UserData as CustomUserData; //var userData = e.HttpClient.UserData as CustomUserData;
//if (ext == ".gif" || ext == ".png" || ext == ".jpg") //if (ext == ".gif" || ext == ".png" || ext == ".jpg")
......
...@@ -615,36 +615,36 @@ namespace Titanium.Web.Proxy.EventArguments ...@@ -615,36 +615,36 @@ namespace Titanium.Web.Proxy.EventArguments
/// <param name="closeServerConnection">Close the server connection used by request if any?</param> /// <param name="closeServerConnection">Close the server connection used by request if any?</param>
public void Respond(Response response, bool closeServerConnection = false) public void Respond(Response response, bool closeServerConnection = false)
{ {
//request already send/ready to be sent. // request already send/ready to be sent.
if (HttpClient.Request.Locked) if (HttpClient.Request.Locked)
{ {
//response already received from server and ready to be sent to client. // response already received from server and ready to be sent to client.
if (HttpClient.Response.Locked) if (HttpClient.Response.Locked)
{ {
throw new Exception("You cannot call this function after response is sent to the client."); throw new Exception("You cannot call this function after response is sent to the client.");
} }
//cleanup original response. // cleanup original response.
if (closeServerConnection) if (closeServerConnection)
{ {
//no need to cleanup original connection. // no need to cleanup original connection.
//it will be closed any way. // it will be closed any way.
TerminateServerConnection(); TerminateServerConnection();
} }
response.SetOriginalHeaders(HttpClient.Response); response.SetOriginalHeaders(HttpClient.Response);
//response already received from server but not yet ready to sent to client. // response already received from server but not yet ready to sent to client.
HttpClient.Response = response; HttpClient.Response = response;
HttpClient.Response.Locked = true; HttpClient.Response.Locked = true;
} }
//request not yet sent/not yet ready to be sent. // request not yet sent/not yet ready to be sent.
else else
{ {
HttpClient.Request.Locked = true; HttpClient.Request.Locked = true;
HttpClient.Request.CancelRequest = true; HttpClient.Request.CancelRequest = true;
//set new response. // set new response.
HttpClient.Response = response; HttpClient.Response = response;
HttpClient.Response.Locked = true; HttpClient.Response.Locked = true;
} }
......
...@@ -134,6 +134,7 @@ namespace Titanium.Web.Proxy ...@@ -134,6 +134,7 @@ namespace Titanium.Web.Proxy
if (decryptSsl && isClientHello) if (decryptSsl && isClientHello)
{ {
clientConnection.SslProtocol = clientHelloInfo.SslProtocol;
connectRequest.RequestUri = new Uri("https://" + httpUrl); connectRequest.RequestUri = new Uri("https://" + httpUrl);
bool http2Supported = false; bool http2Supported = false;
...@@ -151,7 +152,8 @@ namespace Titanium.Web.Proxy ...@@ -151,7 +152,8 @@ namespace Titanium.Web.Proxy
http2Supported = connection.NegotiatedApplicationProtocol == http2Supported = connection.NegotiatedApplicationProtocol ==
SslApplicationProtocol.Http2; SslApplicationProtocol.Http2;
//release connection back to pool instead of closing when connection pool is enabled.
// release connection back to pool instead of closing when connection pool is enabled.
await tcpConnectionFactory.Release(connection, true); await tcpConnectionFactory.Release(connection, true);
} }
catch (Exception) catch (Exception)
...@@ -165,15 +167,15 @@ namespace Titanium.Web.Proxy ...@@ -165,15 +167,15 @@ namespace Titanium.Web.Proxy
IPAddress[] ipAddresses = null; IPAddress[] ipAddresses = null;
try try
{ {
//make sure the host can be resolved before creating the prefetch task // make sure the host can be resolved before creating the prefetch task
ipAddresses = await Dns.GetHostAddressesAsync(connectArgs.HttpClient.Request.RequestUri.Host); ipAddresses = await Dns.GetHostAddressesAsync(connectArgs.HttpClient.Request.RequestUri.Host);
} }
catch (SocketException) { } catch (SocketException) { }
if (ipAddresses != null && ipAddresses.Length > 0) if (ipAddresses != null && ipAddresses.Length > 0)
{ {
//don't pass cancellation token here // don't pass cancellation token here
//it could cause floating server connections when client exits // it could cause floating server connections when client exits
prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs, prefetchConnectionTask = tcpConnectionFactory.GetServerConnection(this, connectArgs,
isConnect: true, applicationProtocols: null, noCache: false, isConnect: true, applicationProtocols: null, noCache: false,
cancellationToken: CancellationToken.None); cancellationToken: CancellationToken.None);
......
...@@ -50,11 +50,11 @@ namespace Titanium.Web.Proxy.Extensions ...@@ -50,11 +50,11 @@ namespace Titanium.Web.Proxy.Extensions
socket.Blocking = false; socket.Blocking = false;
socket.Send(tmp, 0, 0); socket.Send(tmp, 0, 0);
//Connected. // Connected.
} }
catch catch
{ {
//Should we let 10035 == WSAEWOULDBLOCK as valid connection? // Should we let 10035 == WSAEWOULDBLOCK as valid connection?
return false; return false;
} }
finally finally
......
using System;
namespace Titanium.Web.Proxy.Extensions
{
internal static class UriExtensions
{
internal static string GetOriginalPathAndQuery(this Uri uri)
{
string leftPart = uri.GetLeftPart(UriPartial.Authority);
if (uri.OriginalString.StartsWith(leftPart))
return uri.OriginalString.Substring(leftPart.Length);
return uri.IsWellFormedOriginalString() ? uri.PathAndQuery : uri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
}
}
}
...@@ -41,7 +41,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -41,7 +41,7 @@ namespace Titanium.Web.Proxy.Helpers
return true; return true;
} }
//if hostname matches local host name // if hostname matches local host name
if (hostName.Equals(localhostName, StringComparison.OrdinalIgnoreCase)) if (hostName.Equals(localhostName, StringComparison.OrdinalIgnoreCase))
{ {
return true; return true;
......
...@@ -42,7 +42,7 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -42,7 +42,7 @@ namespace Titanium.Web.Proxy.Helpers
public static bool IsMac => isRunningOnMac; public static bool IsMac => isRunningOnMac;
//https://github.com/qmatteoq/DesktopBridgeHelpers/blob/master/DesktopBridge.Helpers/Helpers.cs // https://github.com/qmatteoq/DesktopBridgeHelpers/blob/master/DesktopBridge.Helpers/Helpers.cs
private class UwpHelper private class UwpHelper
{ {
const long APPMODEL_ERROR_NO_PACKAGE = 15700L; const long APPMODEL_ERROR_NO_PACKAGE = 15700L;
......
...@@ -104,8 +104,7 @@ namespace Titanium.Web.Proxy.Http ...@@ -104,8 +104,7 @@ namespace Titanium.Web.Proxy.Http
} }
else else
{ {
var uri = Request.RequestUri; url = Request.RequestUri.GetOriginalPathAndQuery();
url = uri.IsWellFormedOriginalString() ? uri.PathAndQuery : uri.GetComponents(UriComponents.PathAndQuery, UriFormat.Unescaped);
} }
// prepare the request & headers // prepare the request & headers
......
...@@ -105,8 +105,8 @@ namespace Titanium.Web.Proxy.Network.Certificate ...@@ -105,8 +105,8 @@ namespace Titanium.Web.Proxy.Network.Certificate
// KeyLength // KeyLength
const int keyLength = 2048; const int keyLength = 2048;
var graceTime = DateTime.Now.AddDays(graceDays);
var now = DateTime.Now; var now = DateTime.Now;
var graceTime = now.AddDays(graceDays);
var certificate = makeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, hashAlgo, graceTime, var certificate = makeCertificate(isRoot, sSubjectCN, fullSubject, keyLength, hashAlgo, graceTime,
now.AddDays(validDays), isRoot ? null : signingCert); now.AddDays(validDays), isRoot ? null : signingCert);
return certificate; return certificate;
......
...@@ -483,7 +483,7 @@ namespace Titanium.Web.Proxy.Network ...@@ -483,7 +483,7 @@ namespace Titanium.Web.Proxy.Network
var cancellationToken = clearCertificatesTokenSource.Token; var cancellationToken = clearCertificatesTokenSource.Token;
while (!cancellationToken.IsCancellationRequested) while (!cancellationToken.IsCancellationRequested)
{ {
var cutOff = DateTime.Now.AddMinutes(-1 * CertificateCacheTimeOutMinutes); var cutOff = DateTime.Now.AddMinutes(-CertificateCacheTimeOutMinutes);
var outdated = cachedCertificates.Where(x => x.Value.LastAccess < cutOff).ToList(); var outdated = cachedCertificates.Where(x => x.Value.LastAccess < cutOff).ToList();
......
...@@ -37,10 +37,10 @@ namespace Titanium.Web.Proxy.Network ...@@ -37,10 +37,10 @@ namespace Titanium.Web.Proxy.Network
{ {
try try
{ {
//setup connection // setup connection
currentConnection = currentConnection as TcpServerConnection ?? currentConnection = currentConnection as TcpServerConnection ??
await generator(); await generator();
//try // try
@continue = await action(currentConnection); @continue = await action(currentConnection);
} }
...@@ -65,12 +65,12 @@ namespace Titanium.Web.Proxy.Network ...@@ -65,12 +65,12 @@ namespace Titanium.Web.Proxy.Network
return new RetryResult(currentConnection, exception, @continue); return new RetryResult(currentConnection, exception, @continue);
} }
//before retry clear connection // before retry clear connection
private async Task disposeConnection() private async Task disposeConnection()
{ {
if (currentConnection != null) if (currentConnection != null)
{ {
//close connection on error // close connection on error
await tcpConnectionFactory.Release(currentConnection, true); await tcpConnectionFactory.Release(currentConnection, true);
currentConnection = null; currentConnection = null;
} }
......
...@@ -5,6 +5,7 @@ using System.Net; ...@@ -5,6 +5,7 @@ using System.Net;
using System.Net.Security; using System.Net.Security;
#endif #endif
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Authentication;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
...@@ -32,6 +33,8 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -32,6 +33,8 @@ namespace Titanium.Web.Proxy.Network.Tcp
public EndPoint RemoteEndPoint => tcpClient.Client.RemoteEndPoint; public EndPoint RemoteEndPoint => tcpClient.Client.RemoteEndPoint;
internal SslProtocols SslProtocol { get; set; }
internal SslApplicationProtocol NegotiatedApplicationProtocol { get; set; } internal SslApplicationProtocol NegotiatedApplicationProtocol { get; set; }
private readonly TcpClient tcpClient; private readonly TcpClient tcpClient;
...@@ -79,9 +82,9 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -79,9 +82,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
Task.Run(async () => Task.Run(async () =>
{ {
//delay calling tcp connection close() // delay calling tcp connection close()
//so that client have enough time to call close first. // so that client have enough time to call close first.
//This way we can push tcp Time_Wait to client side when possible. // This way we can push tcp Time_Wait to client side when possible.
await Task.Delay(1000); await Task.Delay(1000);
proxyServer.UpdateClientConnectionCount(false); proxyServer.UpdateClientConnectionCount(false);
tcpClient.CloseSocket(); tcpClient.CloseSocket();
......
...@@ -90,9 +90,9 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -90,9 +90,9 @@ namespace Titanium.Web.Proxy.Network.Tcp
{ {
Task.Run(async () => Task.Run(async () =>
{ {
//delay calling tcp connection close() // delay calling tcp connection close()
//so that server have enough time to call close first. // so that server have enough time to call close first.
//This way we can push tcp Time_Wait to server side when possible. // This way we can push tcp Time_Wait to server side when possible.
await Task.Delay(1000); await Task.Delay(1000);
proxyServer.UpdateServerConnectionCount(false); proxyServer.UpdateServerConnectionCount(false);
Stream?.Dispose(); Stream?.Dispose();
......
...@@ -210,7 +210,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security ...@@ -210,7 +210,7 @@ namespace Titanium.Web.Proxy.Network.WinAuth.Security
// int cbBuffer; // int cbBuffer;
// int BufferType; // int BufferType;
// pvBuffer; // pvBuffer;
//What we need to do here is to grab a hold of the pvBuffer allocate by the individual // What we need to do here is to grab a hold of the pvBuffer allocate by the individual
// SecBuffer and release it... // SecBuffer and release it...
int currentOffset = index * Marshal.SizeOf(typeof(Buffer)); int currentOffset = index * Marshal.SizeOf(typeof(Buffer));
var secBufferpvBuffer = Marshal.ReadIntPtr(pBuffers, var secBufferpvBuffer = Marshal.ReadIntPtr(pBuffers,
......
...@@ -122,7 +122,9 @@ namespace Titanium.Web.Proxy ...@@ -122,7 +122,9 @@ namespace Titanium.Web.Proxy
/// </summary> /// </summary>
private SystemProxyManager systemProxySettingsManager { get; } private SystemProxyManager systemProxySettingsManager { get; }
//Number of exception retries when connection pool is enabled. /// <summary>
/// Number of exception retries when connection pool is enabled.
/// </summary>
private int retries => EnableConnectionPool ? MaxCachedConnections : 0; private int retries => EnableConnectionPool ? MaxCachedConnections : 0;
/// <summary> /// <summary>
...@@ -266,7 +268,7 @@ namespace Titanium.Web.Proxy ...@@ -266,7 +268,7 @@ namespace Titanium.Web.Proxy
/// Local adapter/NIC endpoint where proxy makes request via. /// Local adapter/NIC endpoint where proxy makes request via.
/// Defaults via any IP addresses of this machine. /// Defaults via any IP addresses of this machine.
/// </summary> /// </summary>
public IPEndPoint UpStreamEndPoint { get; set; } = new IPEndPoint(IPAddress.Any, 0); public IPEndPoint UpStreamEndPoint { get; set; }
/// <summary> /// <summary>
/// A list of IpAddress and port this proxy is listening to. /// A list of IpAddress and port this proxy is listening to.
...@@ -660,7 +662,7 @@ namespace Titanium.Web.Proxy ...@@ -660,7 +662,7 @@ namespace Titanium.Web.Proxy
{ {
endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port); endPoint.Listener = new TcpListener(endPoint.IpAddress, endPoint.Port);
//linux/macOS has a bug with socket reuse in .net core. // linux/macOS has a bug with socket reuse in .net core.
if (ReuseSocket && RunTime.IsWindows) if (ReuseSocket && RunTime.IsWindows)
{ {
endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); endPoint.Listener.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
...@@ -871,13 +873,13 @@ namespace Titanium.Web.Proxy ...@@ -871,13 +873,13 @@ namespace Titanium.Web.Proxy
/// <returns></returns> /// <returns></returns>
internal async Task InvokeConnectionCreateEvent(TcpClient client, bool isClientConnection) internal async Task InvokeConnectionCreateEvent(TcpClient client, bool isClientConnection)
{ {
//client connection created // client connection created
if (isClientConnection && OnClientConnectionCreate != null) if (isClientConnection && OnClientConnectionCreate != null)
{ {
await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc); await OnClientConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
} }
//server connection created // server connection created
if (!isClientConnection && OnServerConnectionCreate != null) if (!isClientConnection && OnServerConnectionCreate != null)
{ {
await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc); await OnServerConnectionCreate.InvokeAsync(this, client, ExceptionFunc);
......
...@@ -163,7 +163,7 @@ namespace Titanium.Web.Proxy ...@@ -163,7 +163,7 @@ namespace Titanium.Web.Proxy
await args.GetRequestBody(cancellationToken); await args.GetRequestBody(cancellationToken);
} }
//we need this to syphon out data from connection if API user changes them. // we need this to syphon out data from connection if API user changes them.
request.SetOriginalHeaders(); request.SetOriginalHeaders();
args.TimeLine["Request Received"] = DateTime.Now; args.TimeLine["Request Received"] = DateTime.Now;
...@@ -191,7 +191,7 @@ namespace Titanium.Web.Proxy ...@@ -191,7 +191,7 @@ namespace Titanium.Web.Proxy
continue; continue;
} }
//If prefetch task is available. // If prefetch task is available.
if (connection == null && prefetchTask != null) if (connection == null && prefetchTask != null)
{ {
try try
...@@ -226,11 +226,11 @@ namespace Titanium.Web.Proxy ...@@ -226,11 +226,11 @@ namespace Titanium.Web.Proxy
clientConnection.NegotiatedApplicationProtocol, clientConnection.NegotiatedApplicationProtocol,
cancellationToken, cancellationTokenSource); cancellationToken, cancellationTokenSource);
//update connection to latest used // update connection to latest used
connection = result.LatestConnection; connection = result.LatestConnection;
closeServerConnection = !result.Continue; closeServerConnection = !result.Continue;
//throw if exception happened // throw if exception happened
if (!result.IsSuccess) if (!result.IsSuccess)
{ {
throw result.Exception; throw result.Exception;
...@@ -241,7 +241,7 @@ namespace Titanium.Web.Proxy ...@@ -241,7 +241,7 @@ namespace Titanium.Web.Proxy
return; return;
} }
//user requested // user requested
if (args.HttpClient.CloseServerConnection) if (args.HttpClient.CloseServerConnection)
{ {
closeServerConnection = true; closeServerConnection = true;
...@@ -304,13 +304,13 @@ namespace Titanium.Web.Proxy ...@@ -304,13 +304,13 @@ namespace Titanium.Web.Proxy
TcpServerConnection serverConnection, SslApplicationProtocol sslApplicationProtocol, TcpServerConnection serverConnection, SslApplicationProtocol sslApplicationProtocol,
CancellationToken cancellationToken, CancellationTokenSource cancellationTokenSource) CancellationToken cancellationToken, CancellationTokenSource cancellationTokenSource)
{ {
//a connection generator task with captured parameters via closure. // a connection generator task with captured parameters via closure.
Func<Task<TcpServerConnection>> generator = () => Func<Task<TcpServerConnection>> generator = () =>
tcpConnectionFactory.GetServerConnection(this, args, isConnect: false, tcpConnectionFactory.GetServerConnection(this, args, isConnect: false,
applicationProtocol: sslApplicationProtocol, applicationProtocol: sslApplicationProtocol,
noCache: false, cancellationToken: cancellationToken); noCache: false, cancellationToken: cancellationToken);
//for connection pool, retry fails until cache is exhausted. // for connection pool, retry fails until cache is exhausted.
return await retryPolicy<ServerConnectionException>().ExecuteAsync(async (connection) => return await retryPolicy<ServerConnectionException>().ExecuteAsync(async (connection) =>
{ {
args.TimeLine["Connection Ready"] = DateTime.Now; args.TimeLine["Connection Ready"] = DateTime.Now;
...@@ -390,12 +390,12 @@ namespace Titanium.Web.Proxy ...@@ -390,12 +390,12 @@ namespace Titanium.Web.Proxy
{ {
var supportedAcceptEncoding = new List<string>(); var supportedAcceptEncoding = new List<string>();
//only allow proxy supported compressions // only allow proxy supported compressions
supportedAcceptEncoding.AddRange(acceptEncoding.Split(',') supportedAcceptEncoding.AddRange(acceptEncoding.Split(',')
.Select(x => x.Trim()) .Select(x => x.Trim())
.Where(x => ProxyConstants.ProxySupportedCompressions.Contains(x))); .Where(x => ProxyConstants.ProxySupportedCompressions.Contains(x)));
//uncompressed is always supported by proxy // uncompressed is always supported by proxy
supportedAcceptEncoding.Add("identity"); supportedAcceptEncoding.Add("identity");
requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding, requestHeaders.SetOrAddHeaderValue(KnownHeaders.AcceptEncoding,
......
...@@ -51,8 +51,8 @@ namespace Titanium.Web.Proxy ...@@ -51,8 +51,8 @@ namespace Titanium.Web.Proxy
} }
} }
//save original values so that if user changes them // save original values so that if user changes them
//we can still use original values when syphoning out data from attached tcp connection. // we can still use original values when syphoning out data from attached tcp connection.
response.SetOriginalHeaders(); response.SetOriginalHeaders();
// if user requested call back then do it // if user requested call back then do it
...@@ -66,10 +66,10 @@ namespace Titanium.Web.Proxy ...@@ -66,10 +66,10 @@ namespace Titanium.Web.Proxy
var clientStreamWriter = args.ProxyClient.ClientStreamWriter; var clientStreamWriter = args.ProxyClient.ClientStreamWriter;
//user set custom response by ignoring original response from server. // user set custom response by ignoring original response from server.
if (response.Locked) if (response.Locked)
{ {
//write custom user response with body and return. // write custom user response with body and return.
await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken); await clientStreamWriter.WriteResponseAsync(response, cancellationToken: cancellationToken);
if (args.HttpClient.Connection != null if (args.HttpClient.Connection != null
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Authentication;
using System.Text; using System.Text;
using Titanium.Web.Proxy.StreamExtended.Models; using Titanium.Web.Proxy.StreamExtended.Models;
...@@ -51,6 +52,33 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -51,6 +52,33 @@ namespace Titanium.Web.Proxy.StreamExtended
public Dictionary<string, SslExtension> Extensions { get; set; } public Dictionary<string, SslExtension> Extensions { get; set; }
public SslProtocols SslProtocol
{
get
{
int major = MajorVersion;
int minor = MinorVersion;
if (major == 3 && minor == 3)
return SslProtocols.Tls12;
if (major == 3 && minor == 2)
return SslProtocols.Tls11;
if (major == 3 && minor == 1)
return SslProtocols.Tls;
#pragma warning disable 618
if (major == 3 && minor == 0)
return SslProtocols.Ssl3;
if (major == 2 && minor == 0)
return SslProtocols.Ssl2;
#pragma warning restore 618
return SslProtocols.None;
}
}
private static string SslVersionToString(int major, int minor) private static string SslVersionToString(int major, int minor)
{ {
string str = "Unknown"; string str = "Unknown";
......
using System.Diagnostics;
using System.IO;
using System.Threading;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.StreamExtended.Network
{
public class ClientHelloAlpnAdderStream : Stream
{
private readonly CustomBufferedStream stream;
private readonly IBufferPool bufferPool;
private bool called;
public ClientHelloAlpnAdderStream(CustomBufferedStream stream, IBufferPool bufferPool)
{
this.stream = stream;
}
public override void Flush()
{
stream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
return stream.Seek(offset, origin);
}
public override void SetLength(long value)
{
stream.SetLength(value);
}
[DebuggerStepThrough]
public override int Read(byte[] buffer, int offset, int count)
{
return stream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
if (called)
{
stream.Write(buffer, offset, count);
return;
}
called = true;
var ms = new MemoryStream(buffer, offset, count);
//this can be non async, because reads from a memory stream
var cts = new CancellationTokenSource();
var clientHello = SslTools.PeekClientHello(new CustomBufferedStream(ms, bufferPool, (int)ms.Length), bufferPool, cts.Token).Result;
if (clientHello != null)
{
// 0x00 0x10: ALPN identifier
// 0x00 0x0e: length of ALPN data
// 0x00 0x0c: length of ALPN data again:)
var dataToAdd = new byte[]
{
0x0, 0x10, 0x0, 0xE, 0x0, 0xC,
2, (byte)'h', (byte)'2',
8, (byte)'h', (byte)'t', (byte)'t', (byte)'p', (byte)'/', (byte)'1', (byte)'.', (byte)'1'
};
int newByteCount = clientHello.Extensions == null ? dataToAdd.Length + 2 : dataToAdd.Length;
var buffer2 = new byte[buffer.Length + newByteCount];
for (int i = 0; i < buffer.Length; i++)
{
buffer2[i] = buffer[i];
}
//this is a hacky solution, but works
int length = (buffer[offset + 3] << 8) + buffer[offset + 4];
length += newByteCount;
buffer2[offset + 3] = (byte)(length >> 8);
buffer2[offset + 4] = (byte)length;
length = (buffer[offset + 6] << 16) + (buffer[offset + 7] << 8) + buffer[offset + 8];
length += newByteCount;
buffer2[offset + 6] = (byte)(length >> 16);
buffer2[offset + 7] = (byte)(length >> 8);
buffer2[offset + 8] = (byte)length;
int pos = offset + clientHello.EntensionsStartPosition;
int endPos = offset + clientHello.ClientHelloLength;
if (clientHello.Extensions != null)
{
// update ALPN length
length = (buffer[pos] << 8) + buffer[pos + 1];
length += newByteCount;
buffer2[pos] = (byte)(length >> 8);
buffer2[pos + 1] = (byte)length;
}
else
{
// add ALPN length
length = dataToAdd.Length;
buffer2[pos] = (byte)(length >> 8);
buffer2[pos + 1] = (byte)length;
endPos += 2;
}
for (int i = 0; i < dataToAdd.Length; i++)
{
buffer2[endPos + i] = dataToAdd[i];
}
// copy the reamining data if any
for (int i = clientHello.ClientHelloLength; i < count; i++)
{
buffer2[offset + newByteCount + i] = buffer[offset + i];
}
buffer = buffer2;
count += newByteCount;
}
stream.Write(buffer, offset, count);
}
public override bool CanRead => stream.CanRead;
public override bool CanSeek => stream.CanSeek;
public override bool CanWrite => stream.CanWrite;
public override long Length => stream.Length;
public override long Position
{
get => stream.Position;
set => stream.Position = value;
}
}
}
\ No newline at end of file
...@@ -63,7 +63,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -63,7 +63,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
public void Flush() public void Flush()
{ {
//send out the current data from from the buffer // send out the current data from from the buffer
if (bufferLength > 0) if (bufferLength > 0)
{ {
writer.Write(buffer, 0, bufferLength); writer.Write(buffer, 0, bufferLength);
...@@ -73,7 +73,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -73,7 +73,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
public async Task FlushAsync(CancellationToken cancellationToken = default) public async Task FlushAsync(CancellationToken cancellationToken = default)
{ {
//send out the current data from from the buffer // send out the current data from from the buffer
if (bufferLength > 0) if (bufferLength > 0)
{ {
await writer.WriteAsync(buffer, 0, bufferLength, cancellationToken); await writer.WriteAsync(buffer, 0, bufferLength, cancellationToken);
......
...@@ -51,7 +51,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -51,7 +51,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
try try
{ {
var method = typeof(NetworkStream).GetMethod(nameof(Stream.ReadAsync), var method = typeof(NetworkStream).GetMethod(nameof(Stream.ReadAsync),
new Type[] { typeof(byte[]), typeof(int), typeof(int), typeof(CancellationToken) }); new[] { typeof(byte[]), typeof(int), typeof(int), typeof(CancellationToken) });
if (method != null && method.DeclaringType != typeof(Stream)) if (method != null && method.DeclaringType != typeof(Stream))
{ {
networkStreamHack = false; networkStreamHack = false;
...@@ -257,13 +257,13 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -257,13 +257,13 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
await FillBufferAsync(cancellationToken); await FillBufferAsync(cancellationToken);
} }
//When index is greater than the buffer size // When index is greater than the buffer size
if (streamBuffer.Length <= index) if (streamBuffer.Length <= index)
{ {
throw new Exception("Requested Peek index exceeds the buffer size. Consider increasing the buffer size."); throw new Exception("Requested Peek index exceeds the buffer size. Consider increasing the buffer size.");
} }
//When index is greater than the buffer size // When index is greater than the buffer size
if (Available <= index) if (Available <= index)
{ {
return -1; return -1;
...@@ -287,7 +287,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -287,7 +287,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
await FillBufferAsync(cancellationToken); await FillBufferAsync(cancellationToken);
} }
//When index is greater than the buffer size // When index is greater than the buffer size
if (streamBuffer.Length <= (index + size)) if (streamBuffer.Length <= (index + size))
{ {
throw new Exception("Requested Peek index and size exceeds the buffer size. Consider increasing the buffer size."); throw new Exception("Requested Peek index and size exceeds the buffer size. Consider increasing the buffer size.");
...@@ -477,8 +477,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -477,8 +477,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
if (bufferLength > 0) if (bufferLength > 0)
{ {
//normally we fill the buffer only when it is empty, but sometimes we need more data // normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remaining data to the beginning of the buffer // move the remaining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength); Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
} }
...@@ -521,8 +521,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -521,8 +521,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
if (bufferLength > 0) if (bufferLength > 0)
{ {
//normally we fill the buffer only when it is empty, but sometimes we need more data // normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remaining data to the beginning of the buffer // move the remaining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength); Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
} }
...@@ -586,7 +586,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -586,7 +586,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
byte newChar = reader.ReadByteFromBuffer(); byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar; buffer[bufferDataLength] = newChar;
//if new line // if new line
if (newChar == '\n') if (newChar == '\n')
{ {
if (lastChar == '\r') if (lastChar == '\r')
...@@ -599,7 +599,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -599,7 +599,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
bufferDataLength++; bufferDataLength++;
//store last char for new line comparison // store last char for new line comparison
lastChar = newChar; lastChar = newChar;
if (bufferDataLength == buffer.Length) if (bufferDataLength == buffer.Length)
...@@ -663,8 +663,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -663,8 +663,8 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
vAsyncResult.ContinueWith(pAsyncResult => vAsyncResult.ContinueWith(pAsyncResult =>
{ {
//use TaskExtended to pass State as AsyncObject // use TaskExtended to pass State as AsyncObject
//callback will call EndRead (otherwise, it will block) // callback will call EndRead (otherwise, it will block)
callback?.Invoke(new TaskResult<int>(pAsyncResult, state)); callback?.Invoke(new TaskResult<int>(pAsyncResult, state));
}); });
...@@ -685,7 +685,6 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -685,7 +685,6 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
return ((TaskResult<int>)asyncResult).Result; return ((TaskResult<int>)asyncResult).Result;
} }
/// <summary> /// <summary>
/// Fix the .net bug with SslStream slow WriteAsync /// Fix the .net bug with SslStream slow WriteAsync
/// https://github.com/justcoding121/Titanium-Web-Proxy/issues/495 /// https://github.com/justcoding121/Titanium-Web-Proxy/issues/495
...@@ -709,6 +708,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network ...@@ -709,6 +708,7 @@ namespace Titanium.Web.Proxy.StreamExtended.Network
return vAsyncResult; return vAsyncResult;
} }
public override void EndWrite(IAsyncResult asyncResult) public override void EndWrite(IAsyncResult asyncResult)
{ {
if (!networkStreamHack) if (!networkStreamHack)
......
using System.Diagnostics;
using System.IO;
using System.Threading;
using Titanium.Web.Proxy.StreamExtended.BufferPool;
namespace Titanium.Web.Proxy.StreamExtended.Network
{
public class ServerHelloAlpnAdderStream : Stream
{
private readonly IBufferPool bufferPool;
private readonly CustomBufferedStream stream;
private bool called;
public ServerHelloAlpnAdderStream(CustomBufferedStream stream, IBufferPool bufferPool)
{
this.bufferPool = bufferPool;
this.stream = stream;
}
public override void Flush()
{
stream.Flush();
}
public override long Seek(long offset, SeekOrigin origin)
{
return stream.Seek(offset, origin);
}
public override void SetLength(long value)
{
stream.SetLength(value);
}
[DebuggerStepThrough]
public override int Read(byte[] buffer, int offset, int count)
{
return stream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
if (called)
{
stream.Write(buffer, offset, count);
return;
}
called = true;
var ms = new MemoryStream(buffer, offset, count);
//this can be non async, because reads from a memory stream
var cts = new CancellationTokenSource();
var serverHello = SslTools.PeekServerHello(new CustomBufferedStream(ms, bufferPool, (int)ms.Length), bufferPool, cts.Token).Result;
if (serverHello != null)
{
// 0x00 0x10: ALPN identifier
// 0x00 0x0e: length of ALPN data
// 0x00 0x0c: length of ALPN data again:)
var dataToAdd = new byte[]
{
0x0, 0x10, 0x0, 0x5, 0x0, 0x3,
2, (byte)'h', (byte)'2'
};
int newByteCount = serverHello.Extensions == null ? dataToAdd.Length + 2 : dataToAdd.Length;
var buffer2 = new byte[buffer.Length + newByteCount];
for (int i = 0; i < buffer.Length; i++)
{
buffer2[i] = buffer[i];
}
//this is a hacky solution, but works
int length = (buffer[offset + 3] << 8) + buffer[offset + 4];
length += newByteCount;
buffer2[offset + 3] = (byte)(length >> 8);
buffer2[offset + 4] = (byte)length;
length = (buffer[offset + 6] << 16) + (buffer[offset + 7] << 8) + buffer[offset + 8];
length += newByteCount;
buffer2[offset + 6] = (byte)(length >> 16);
buffer2[offset + 7] = (byte)(length >> 8);
buffer2[offset + 8] = (byte)length;
int pos = offset + serverHello.EntensionsStartPosition;
int endPos = offset + serverHello.ServerHelloLength;
if (serverHello.Extensions != null)
{
// update ALPN length
length = (buffer[pos] << 8) + buffer[pos + 1];
length += newByteCount;
buffer2[pos] = (byte)(length >> 8);
buffer2[pos + 1] = (byte)length;
}
else
{
// add ALPN length
length = dataToAdd.Length;
buffer2[pos] = (byte)(length >> 8);
buffer2[pos + 1] = (byte)length;
endPos += 2;
}
for (int i = 0; i < dataToAdd.Length; i++)
{
buffer2[endPos + i] = dataToAdd[i];
}
// copy the reamining data if any
for (int i = serverHello.ServerHelloLength; i < count; i++)
{
buffer2[offset + newByteCount + i] = buffer[offset + i];
}
buffer = buffer2;
count += newByteCount;
}
stream.Write(buffer, offset, count);
}
public override bool CanRead => stream.CanRead;
public override bool CanSeek => stream.CanSeek;
public override bool CanWrite => stream.CanWrite;
public override long Length => stream.Length;
public override long Position
{
get => stream.Position;
set => stream.Position = value;
}
}
}
\ No newline at end of file
...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -16,7 +16,7 @@ namespace Titanium.Web.Proxy.StreamExtended
private static string GetExtensionData(int value, byte[] data) private static string GetExtensionData(int value, byte[] data)
{ {
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value) switch (value)
{ {
case 0: case 0:
...@@ -59,8 +59,8 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -59,8 +59,8 @@ namespace Titanium.Web.Proxy.StreamExtended
private static string GetSupportedGroup(byte[] data) private static string GetSupportedGroup(byte[] data)
{ {
//https://datatracker.ietf.org/doc/draft-ietf-tls-rfc4492bis/?include_text=1 // https://datatracker.ietf.org/doc/draft-ietf-tls-rfc4492bis/?include_text=1
List<string> list = new List<string>(); var list = new List<string>();
if (data.Length < 2) if (data.Length < 2)
{ {
return string.Empty; return string.Empty;
...@@ -73,70 +73,70 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -73,70 +73,70 @@ namespace Titanium.Web.Proxy.StreamExtended
switch (namedCurve) switch (namedCurve)
{ {
case 1: case 1:
list.Add("sect163k1 [0x1]"); //deprecated list.Add("sect163k1 [0x1]"); // deprecated
break; break;
case 2: case 2:
list.Add("sect163r1 [0x2]"); //deprecated list.Add("sect163r1 [0x2]"); // deprecated
break; break;
case 3: case 3:
list.Add("sect163r2 [0x3]"); //deprecated list.Add("sect163r2 [0x3]"); // deprecated
break; break;
case 4: case 4:
list.Add("sect193r1 [0x4]"); //deprecated list.Add("sect193r1 [0x4]"); // deprecated
break; break;
case 5: case 5:
list.Add("sect193r2 [0x5]"); //deprecated list.Add("sect193r2 [0x5]"); // deprecated
break; break;
case 6: case 6:
list.Add("sect233k1 [0x6]"); //deprecated list.Add("sect233k1 [0x6]"); // deprecated
break; break;
case 7: case 7:
list.Add("sect233r1 [0x7]"); //deprecated list.Add("sect233r1 [0x7]"); // deprecated
break; break;
case 8: case 8:
list.Add("sect239k1 [0x8]"); //deprecated list.Add("sect239k1 [0x8]"); // deprecated
break; break;
case 9: case 9:
list.Add("sect283k1 [0x9]"); //deprecated list.Add("sect283k1 [0x9]"); // deprecated
break; break;
case 10: case 10:
list.Add("sect283r1 [0xA]"); //deprecated list.Add("sect283r1 [0xA]"); // deprecated
break; break;
case 11: case 11:
list.Add("sect409k1 [0xB]"); //deprecated list.Add("sect409k1 [0xB]"); // deprecated
break; break;
case 12: case 12:
list.Add("sect409r1 [0xC]"); //deprecated list.Add("sect409r1 [0xC]"); // deprecated
break; break;
case 13: case 13:
list.Add("sect571k1 [0xD]"); //deprecated list.Add("sect571k1 [0xD]"); // deprecated
break; break;
case 14: case 14:
list.Add("sect571r1 [0xE]"); //deprecated list.Add("sect571r1 [0xE]"); // deprecated
break; break;
case 15: case 15:
list.Add("secp160k1 [0xF]"); //deprecated list.Add("secp160k1 [0xF]"); // deprecated
break; break;
case 16: case 16:
list.Add("secp160r1 [0x10]"); //deprecated list.Add("secp160r1 [0x10]"); // deprecated
break; break;
case 17: case 17:
list.Add("secp160r2 [0x11]"); //deprecated list.Add("secp160r2 [0x11]"); // deprecated
break; break;
case 18: case 18:
list.Add("secp192k1 [0x12]"); //deprecated list.Add("secp192k1 [0x12]"); // deprecated
break; break;
case 19: case 19:
list.Add("secp192r1 [0x13]"); //deprecated list.Add("secp192r1 [0x13]"); // deprecated
break; break;
case 20: case 20:
list.Add("secp224k1 [0x14]"); //deprecated list.Add("secp224k1 [0x14]"); // deprecated
break; break;
case 21: case 21:
list.Add("secp224r1 [0x15]"); //deprecated list.Add("secp224r1 [0x15]"); // deprecated
break; break;
case 22: case 22:
list.Add("secp256k1 [0x16]"); //deprecated list.Add("secp256k1 [0x16]"); // deprecated
break; break;
case 23: case 23:
list.Add("secp256r1 [0x17]"); list.Add("secp256r1 [0x17]");
...@@ -178,10 +178,10 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -178,10 +178,10 @@ namespace Titanium.Web.Proxy.StreamExtended
list.Add("ffdhe8192 [0x0104]"); list.Add("ffdhe8192 [0x0104]");
break; break;
case 65281: case 65281:
list.Add("arbitrary_explicit_prime_curves [0xFF01]"); //deprecated list.Add("arbitrary_explicit_prime_curves [0xFF01]"); // deprecated
break; break;
case 65282: case 65282:
list.Add("arbitrary_explicit_char2_curves [0xFF02]"); //deprecated list.Add("arbitrary_explicit_char2_curves [0xFF02]"); // deprecated
break; break;
default: default:
list.Add($"unknown [0x{namedCurve:X4}]"); list.Add($"unknown [0x{namedCurve:X4}]");
...@@ -318,7 +318,7 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -318,7 +318,7 @@ namespace Titanium.Web.Proxy.StreamExtended
private static string GetExtensionName(int value) private static string GetExtensionName(int value)
{ {
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml // https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value) switch (value)
{ {
case 0: case 0:
......
...@@ -34,8 +34,8 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -34,8 +34,8 @@ namespace Titanium.Web.Proxy.StreamExtended
/// <returns></returns> /// <returns></returns>
public static async Task<ClientHelloInfo> PeekClientHello(CustomBufferedStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default) public static async Task<ClientHelloInfo> PeekClientHello(CustomBufferedStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{ {
//detects the HTTPS ClientHello message as it is described in the following url: // detects the HTTPS ClientHello message as it is described in the following url:
//https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format // https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
int recordType = await clientStream.PeekByteAsync(0, cancellationToken); int recordType = await clientStream.PeekByteAsync(0, cancellationToken);
if (recordType == -1) if (recordType == -1)
...@@ -45,7 +45,7 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -45,7 +45,7 @@ namespace Titanium.Web.Proxy.StreamExtended
if ((recordType & 0x80) == 0x80) if ((recordType & 0x80) == 0x80)
{ {
//SSL 2 // SSL 2
var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1); var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1);
// length value + minimum length // length value + minimum length
...@@ -105,14 +105,14 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -105,14 +105,14 @@ namespace Titanium.Web.Proxy.StreamExtended
{ {
var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1); var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1);
//should contain at least 43 bytes // should contain at least 43 bytes
// 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length // 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length
if (!await peekStream.EnsureBufferLength(43, cancellationToken)) if (!await peekStream.EnsureBufferLength(43, cancellationToken))
{ {
return null; return null;
} }
//SSL 3.0 or TLS 1.0, 1.1 and 1.2 // SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte(); int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte(); int minorVersion = peekStream.ReadByte();
...@@ -220,8 +220,8 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -220,8 +220,8 @@ namespace Titanium.Web.Proxy.StreamExtended
/// <returns></returns> /// <returns></returns>
public static async Task<ServerHelloInfo> PeekServerHello(CustomBufferedStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default) public static async Task<ServerHelloInfo> PeekServerHello(CustomBufferedStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default)
{ {
//detects the HTTPS ClientHello message as it is described in the following url: // detects the HTTPS ClientHello message as it is described in the following url:
//https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format // https://stackoverflow.com/questions/3897883/how-to-detect-an-incoming-ssl-https-handshake-ssl-wire-format
int recordType = await serverStream.PeekByteAsync(0, cancellationToken); int recordType = await serverStream.PeekByteAsync(0, cancellationToken);
if (recordType == -1) if (recordType == -1)
...@@ -231,7 +231,7 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -231,7 +231,7 @@ namespace Titanium.Web.Proxy.StreamExtended
if ((recordType & 0x80) == 0x80) if ((recordType & 0x80) == 0x80)
{ {
//SSL 2 // SSL 2
// not tested. SSL2 is deprecated // not tested. SSL2 is deprecated
var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1); var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1);
...@@ -284,14 +284,14 @@ namespace Titanium.Web.Proxy.StreamExtended ...@@ -284,14 +284,14 @@ namespace Titanium.Web.Proxy.StreamExtended
{ {
var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1); var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1);
//should contain at least 43 bytes // should contain at least 43 bytes
// 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length // 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length
if (!await peekStream.EnsureBufferLength(43, cancellationToken)) if (!await peekStream.EnsureBufferLength(43, cancellationToken))
{ {
return null; return null;
} }
//SSL 3.0 or TLS 1.0, 1.1 and 1.2 // SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte(); int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte(); int minorVersion = peekStream.ReadByte();
......
...@@ -62,8 +62,9 @@ namespace Titanium.Web.Proxy ...@@ -62,8 +62,9 @@ namespace Titanium.Web.Proxy
if (endPoint.DecryptSsl && args.DecryptSsl) if (endPoint.DecryptSsl && args.DecryptSsl)
{ {
clientConnection.SslProtocol = clientHelloInfo.SslProtocol;
//do client authentication using certificate // do client authentication using certificate
X509Certificate2 certificate = null; X509Certificate2 certificate = null;
try try
{ {
......
...@@ -13,7 +13,7 @@ using System.Threading.Tasks; ...@@ -13,7 +13,7 @@ using System.Threading.Tasks;
namespace Titanium.Web.Proxy.IntegrationTests.Setup namespace Titanium.Web.Proxy.IntegrationTests.Setup
{ {
//set up a kestrel test server // set up a kestrel test server
public class TestServer : IDisposable public class TestServer : IDisposable
{ {
public string ListeningHttpUrl => $"http://localhost:{HttpListeningPort}"; public string ListeningHttpUrl => $"http://localhost:{HttpListeningPort}";
......
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