Commit 2d782ba9 authored by Honfika's avatar Honfika

Parse server hello + minor impovements

parent c35969c1
...@@ -112,18 +112,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -112,18 +112,16 @@ namespace Titanium.Web.Proxy.Helpers
/// Usefull for websocket requests /// Usefull for websocket requests
/// </summary> /// </summary>
/// <param name="clientStream"></param> /// <param name="clientStream"></param>
/// <param name="connection"></param> /// <param name="serverStream"></param>
/// <param name="onDataSend"></param> /// <param name="onDataSend"></param>
/// <param name="onDataReceive"></param> /// <param name="onDataReceive"></param>
/// <returns></returns> /// <returns></returns>
internal static async Task SendRaw(Stream clientStream, TcpConnection connection, Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive) internal static async Task SendRaw(Stream clientStream, Stream serverStream, Action<byte[], int, int> onDataSend, Action<byte[], int, int> onDataReceive)
{ {
var tunnelStream = connection.Stream;
//Now async relay all server=>client & client=>server data //Now async relay all server=>client & client=>server data
var sendRelay = clientStream.CopyToAsync(tunnelStream, onDataSend); var sendRelay = clientStream.CopyToAsync(serverStream, onDataSend);
var receiveRelay = tunnelStream.CopyToAsync(clientStream, onDataReceive); var receiveRelay = serverStream.CopyToAsync(clientStream, onDataReceive);
await Task.WhenAll(sendRelay, receiveRelay); await Task.WhenAll(sendRelay, receiveRelay);
} }
......
...@@ -41,7 +41,7 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -41,7 +41,7 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <summary> /// <summary>
/// Server stream /// Server stream
/// </summary> /// </summary>
internal Stream Stream { get; set; } internal CustomBufferedStream Stream { get; set; }
/// <summary> /// <summary>
/// Last time this connection was used /// Last time this connection was used
......
...@@ -22,12 +22,13 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -22,12 +22,13 @@ namespace Titanium.Web.Proxy.Network.Tcp
/// <param name="remoteHostName"></param> /// <param name="remoteHostName"></param>
/// <param name="remotePort"></param> /// <param name="remotePort"></param>
/// <param name="httpVersion"></param> /// <param name="httpVersion"></param>
/// <param name="isConnect"></param>
/// <param name="isHttps"></param> /// <param name="isHttps"></param>
/// <param name="externalHttpProxy"></param> /// <param name="externalHttpProxy"></param>
/// <param name="externalHttpsProxy"></param> /// <param name="externalHttpsProxy"></param>
/// <returns></returns> /// <returns></returns>
internal async Task<TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps, internal async Task<TcpConnection> CreateClient(ProxyServer server, string remoteHostName, int remotePort, Version httpVersion, bool isHttps,
ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy) bool isConnect, ExternalProxy externalHttpProxy, ExternalProxy externalHttpsProxy)
{ {
bool useUpstreamProxy = false; bool useUpstreamProxy = false;
var externalProxy = isHttps ? externalHttpsProxy : externalHttpProxy; var externalProxy = isHttps ? externalHttpsProxy : externalHttpProxy;
...@@ -60,45 +61,46 @@ namespace Titanium.Web.Proxy.Network.Tcp ...@@ -60,45 +61,46 @@ namespace Titanium.Web.Proxy.Network.Tcp
if (useUpstreamProxy) if (useUpstreamProxy)
{ {
await client.ConnectAsync(externalProxy.HostName, externalProxy.Port); await client.ConnectAsync(externalProxy.HostName, externalProxy.Port);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize); }
else
{
await client.ConnectAsync(remoteHostName, remotePort);
}
if (isHttps) stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
if (useUpstreamProxy && (isConnect || isHttps))
{
using (var writer = new HttpRequestWriter(stream))
{ {
using (var writer = new HttpRequestWriter(stream)) await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}");
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}");
await writer.WriteLineAsync("Connection: Keep-Alive");
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{ {
await writer.WriteLineAsync($"CONNECT {remoteHostName}:{remotePort} HTTP/{httpVersion}"); await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync($"Host: {remoteHostName}:{remotePort}"); await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
await writer.WriteLineAsync("Connection: Keep-Alive"); Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
if (!string.IsNullOrEmpty(externalProxy.UserName) && externalProxy.Password != null)
{
await HttpHeader.ProxyConnectionKeepAlive.WriteToStreamAsync(writer);
await writer.WriteLineAsync("Proxy-Authorization" + ": Basic " +
Convert.ToBase64String(Encoding.UTF8.GetBytes(
externalProxy.UserName + ":" + externalProxy.Password)));
}
await writer.WriteLineAsync();
await writer.FlushAsync();
} }
using (var reader = new CustomBinaryReader(stream, server.BufferSize)) await writer.WriteLineAsync();
{ await writer.FlushAsync();
string result = await reader.ReadLineAsync(); }
if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s))) using (var reader = new CustomBinaryReader(stream, server.BufferSize))
{ {
throw new Exception("Upstream proxy failed to create a secure tunnel"); string result = await reader.ReadLineAsync();
}
await reader.ReadAndIgnoreAllLinesAsync(); if (!new[] { "200 OK", "connection established" }.Any(s => result.ContainsIgnoreCase(s)))
{
throw new Exception("Upstream proxy failed to create a secure tunnel");
} }
await reader.ReadAndIgnoreAllLinesAsync();
} }
} }
else
{
await client.ConnectAsync(remoteHostName, remotePort);
stream = new CustomBufferedStream(client.GetStream(), server.BufferSize);
}
if (isHttps) if (isHttps)
{ {
......
...@@ -125,6 +125,7 @@ namespace Titanium.Web.Proxy ...@@ -125,6 +125,7 @@ namespace Titanium.Web.Proxy
if (!excluded && isClientHello) if (!excluded && isClientHello)
{ {
httpRemoteUri = new Uri("https://" + httpUrl); httpRemoteUri = new Uri("https://" + httpUrl);
connectRequest.RequestUri = httpRemoteUri;
SslStream sslStream = null; SslStream sslStream = null;
...@@ -159,9 +160,24 @@ namespace Titanium.Web.Proxy ...@@ -159,9 +160,24 @@ namespace Titanium.Web.Proxy
else else
{ {
//create new connection //create new connection
using (var connection = await GetServerConnection(connectArgs)) using (var connection = await GetServerConnection(connectArgs, true))
{ {
await TcpHelper.SendRaw(clientStream, connection, if (isClientHello)
{
if (clientStream.Available > 0)
{
//send the buffered data
var data = new byte[clientStream.Available];
await clientStream.ReadAsync(data, 0, data.Length);
await connection.Stream.WriteAsync(data, 0, data.Length);
await connection.Stream.FlushAsync();
}
var serverHelloInfo = await SslTools.GetServerHelloInfo(connection.Stream);
((ConnectResponse)connectArgs.WebSession.Response).ServerHelloInfo = serverHelloInfo;
}
await TcpHelper.SendRaw(clientStream, connection.Stream,
(buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); }); (buffer, offset, count) => { connectArgs.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { connectArgs.OnDataReceived(buffer, offset, count); });
UpdateServerConnectionCount(false); UpdateServerConnectionCount(false);
} }
...@@ -336,16 +352,17 @@ namespace Titanium.Web.Proxy ...@@ -336,16 +352,17 @@ namespace Titanium.Web.Proxy
break; break;
} }
if (connection == null)
{
connection = await GetServerConnection(args);
}
//create a new connection if hostname changes //create a new connection if hostname changes
else if (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)) if (connection != null && !connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase))
{ {
connection.Dispose(); connection.Dispose();
UpdateServerConnectionCount(false); UpdateServerConnectionCount(false);
connection = await GetServerConnection(args); connection = null;
}
if (connection == null)
{
connection = await GetServerConnection(args, false);
} }
//if upgrading to websocket then relay the requet without reading the contents //if upgrading to websocket then relay the requet without reading the contents
...@@ -383,7 +400,7 @@ namespace Titanium.Web.Proxy ...@@ -383,7 +400,7 @@ namespace Titanium.Web.Proxy
await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc); await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc);
} }
await TcpHelper.SendRaw(clientStream, connection, await TcpHelper.SendRaw(clientStream, connection.Stream,
(buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }); (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); });
args.Dispose(); args.Dispose();
...@@ -550,8 +567,9 @@ namespace Titanium.Web.Proxy ...@@ -550,8 +567,9 @@ namespace Titanium.Web.Proxy
/// Create a Server Connection /// Create a Server Connection
/// </summary> /// </summary>
/// <param name="args"></param> /// <param name="args"></param>
/// <param name="isConnect"></param>
/// <returns></returns> /// <returns></returns>
private async Task<TcpConnection> GetServerConnection(SessionEventArgs args) private async Task<TcpConnection> GetServerConnection(SessionEventArgs args, bool isConnect)
{ {
ExternalProxy customUpStreamHttpProxy = null; ExternalProxy customUpStreamHttpProxy = null;
ExternalProxy customUpStreamHttpsProxy = null; ExternalProxy customUpStreamHttpsProxy = null;
...@@ -578,7 +596,7 @@ namespace Titanium.Web.Proxy ...@@ -578,7 +596,7 @@ namespace Titanium.Web.Proxy
args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Host,
args.WebSession.Request.RequestUri.Port, args.WebSession.Request.RequestUri.Port,
args.WebSession.Request.HttpVersion, args.WebSession.Request.HttpVersion,
args.IsHttps, args.IsHttps, isConnect,
customUpStreamHttpProxy ?? UpStreamHttpProxy, customUpStreamHttpProxy ?? UpStreamHttpProxy,
customUpStreamHttpsProxy ?? UpStreamHttpsProxy); customUpStreamHttpsProxy ?? UpStreamHttpsProxy);
} }
......
...@@ -85,24 +85,25 @@ namespace Titanium.Web.Proxy.Ssl ...@@ -85,24 +85,25 @@ namespace Titanium.Web.Proxy.Ssl
if (CompressionData.Length > 0) if (CompressionData.Length > 0)
{ {
int id = CompressionData[0]; int compressionMethod = CompressionData[0];
string compression = null; string compression = compressions.Length > compressionMethod
compression = compressions.Length > id ? compressions[id] : $"unknown [0x{id:X2}]"; ? compressions[compressionMethod]
: $"unknown [0x{compressionMethod:X2}]";
sb.AppendLine($"Compression: {compression}"); sb.AppendLine($"Compression: {compression}");
} }
if (Ciphers.Length > 0) if (Ciphers.Length > 0)
{ {
sb.AppendLine($"Ciphers:"); sb.AppendLine("Ciphers:");
foreach (int cipher in Ciphers) foreach (int cipherSuite in Ciphers)
{ {
string cipherStr; string cipherStr;
if (!SslCiphers.Ciphers.TryGetValue(cipher, out cipherStr)) if (!SslCiphers.Ciphers.TryGetValue(cipherSuite, out cipherStr))
{ {
cipherStr = $"unknown"; cipherStr = "unknown";
} }
sb.AppendLine($"[0x{cipher:X4}] {cipherStr}"); sb.AppendLine($"[0x{cipherSuite:X4}] {cipherStr}");
} }
} }
......
...@@ -35,9 +35,9 @@ namespace Titanium.Web.Proxy.Ssl ...@@ -35,9 +35,9 @@ namespace Titanium.Web.Proxy.Ssl
public byte[] SessionId { get; set; } public byte[] SessionId { get; set; }
public int[] Ciphers { get; set; } public int CipherSuite { get; set; }
public byte[] CompressionData { get; set; } public byte CompressionMethod { get; set; }
public List<SslExtension> Extensions { get; set; } public List<SslExtension> Extensions { get; set; }
...@@ -83,29 +83,20 @@ namespace Titanium.Web.Proxy.Ssl ...@@ -83,29 +83,20 @@ namespace Titanium.Web.Proxy.Ssl
} }
} }
if (CompressionData.Length > 0) string compression = compressions.Length > CompressionMethod
{ ? compressions[CompressionMethod]
int id = CompressionData[0]; : $"unknown [0x{CompressionMethod:X2}]";
string compression = null; sb.AppendLine($"Compression: {compression}");
compression = compressions.Length > id ? compressions[id] : $"unknown [0x{id:X2}]";
sb.AppendLine($"Compression: {compression}");
}
if (Ciphers.Length > 0) sb.Append("Cipher:");
string cipherStr;
if (!SslCiphers.Ciphers.TryGetValue(CipherSuite, out cipherStr))
{ {
sb.AppendLine($"Ciphers:"); cipherStr = "unknown";
foreach (int cipher in Ciphers)
{
string cipherStr;
if (!SslCiphers.Ciphers.TryGetValue(cipher, out cipherStr))
{
cipherStr = $"unknown";
}
sb.AppendLine($"[0x{cipher:X4}] {cipherStr}");
}
} }
sb.AppendLine($"[0x{CipherSuite:X4}] {cipherStr}");
return sb.ToString(); return sb.ToString();
} }
} }
......
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
...@@ -108,26 +109,7 @@ namespace Titanium.Web.Proxy.Ssl ...@@ -108,26 +109,7 @@ namespace Titanium.Web.Proxy.Ssl
byte[] compressionData = peekStream.ReadBytes(length); byte[] compressionData = peekStream.ReadBytes(length);
List<SslExtension> extensions = null; var extensions = await ReadExtensions(majorVersion, minorVersion, peekStream);
if (majorVersion > 3 || majorVersion == 3 && minorVersion >= 1)
{
if (await peekStream.EnsureBufferLength(2))
{
length = peekStream.ReadInt16();
if (await peekStream.EnsureBufferLength(length))
{
extensions = new List<SslExtension>();
while (peekStream.Available > 3)
{
int id = peekStream.ReadInt16();
length = peekStream.ReadInt16();
byte[] data = peekStream.ReadBytes(length);
extensions.Add(SslExtensions.GetExtension(id, data));
}
}
}
}
var clientHelloInfo = new ClientHelloInfo var clientHelloInfo = new ClientHelloInfo
{ {
...@@ -146,6 +128,33 @@ namespace Titanium.Web.Proxy.Ssl ...@@ -146,6 +128,33 @@ namespace Titanium.Web.Proxy.Ssl
return null; return null;
} }
private static async Task<List<SslExtension>> ReadExtensions(int majorVersion, int minorVersion, CustomBufferedPeekStream peekStream)
{
List<SslExtension> extensions = null;
if (majorVersion > 3 || majorVersion == 3 && minorVersion >= 1)
{
if (await peekStream.EnsureBufferLength(2))
{
int extensionsLength = peekStream.ReadInt16();
if (await peekStream.EnsureBufferLength(extensionsLength))
{
extensions = new List<SslExtension>();
while (extensionsLength > 3)
{
int id = peekStream.ReadInt16();
int length = peekStream.ReadInt16();
byte[] data = peekStream.ReadBytes(length);
extensions.Add(SslExtensions.GetExtension(id, data));
extensionsLength -= 4 + length;
}
}
}
}
return extensions;
}
public static async Task<bool> IsServerHello(CustomBufferedStream serverStream) public static async Task<bool> IsServerHello(CustomBufferedStream serverStream)
{ {
var serverHello = await GetServerHelloInfo(serverStream); var serverHello = await GetServerHelloInfo(serverStream);
...@@ -154,8 +163,6 @@ namespace Titanium.Web.Proxy.Ssl ...@@ -154,8 +163,6 @@ namespace Titanium.Web.Proxy.Ssl
public static async Task<ServerHelloInfo> GetServerHelloInfo(CustomBufferedStream serverStream) public static async Task<ServerHelloInfo> GetServerHelloInfo(CustomBufferedStream serverStream)
{ {
//todo: THIS IS NOT READY YET
//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
...@@ -200,9 +207,13 @@ namespace Titanium.Web.Proxy.Ssl ...@@ -200,9 +207,13 @@ namespace Titanium.Web.Proxy.Ssl
int length = peekStream.ReadInt16(); int length = peekStream.ReadInt16();
if (peekStream.ReadByte() != 0x01) var ps0 = new CustomBufferedPeekStream(serverStream, 5);
var data0 = ps0.ReadBytes(length);
var str = string.Join(" ", data0.Select(x=>x.ToString("X2")));
if (peekStream.ReadByte() != 0x02)
{ {
// should be ClientHello // should be ServerHello
return null; return null;
} }
...@@ -214,63 +225,18 @@ namespace Titanium.Web.Proxy.Ssl ...@@ -214,63 +225,18 @@ namespace Titanium.Web.Proxy.Ssl
byte[] random = peekStream.ReadBytes(32); byte[] random = peekStream.ReadBytes(32);
length = peekStream.ReadByte(); length = peekStream.ReadByte();
// sessionid + 2 ciphersData length // sessionid + cipherSuite + compressionMethod
if (!await peekStream.EnsureBufferLength(length + 2)) if (!await peekStream.EnsureBufferLength(length + 2 + 1))
{ {
return null; return null;
} }
byte[] sessionId = peekStream.ReadBytes(length); byte[] sessionId = peekStream.ReadBytes(length);
length = peekStream.ReadInt16(); int cipherSuite = peekStream.ReadInt16();
byte compressionMethod = peekStream.ReadByte();
// ciphersData + compressionData length var extensions = await ReadExtensions(majorVersion, minorVersion, peekStream);
if (!await peekStream.EnsureBufferLength(length + 1))
{
return null;
}
byte[] ciphersData = peekStream.ReadBytes(length);
int[] ciphers = new int[ciphersData.Length / 2];
for (int i = 0; i < ciphers.Length; i++)
{
ciphers[i] = (ciphersData[2 * i] << 8) + ciphersData[2 * i + 1];
}
length = peekStream.ReadByte();
if (length < 1)
{
return null;
}
// compressionData
if (!await peekStream.EnsureBufferLength(length))
{
return null;
}
byte[] compressionData = peekStream.ReadBytes(length);
List<SslExtension> extensions = null;
if (majorVersion > 3 || majorVersion == 3 && minorVersion >= 1)
{
if (await peekStream.EnsureBufferLength(2))
{
length = peekStream.ReadInt16();
if (await peekStream.EnsureBufferLength(length))
{
extensions = new List<SslExtension>();
while (peekStream.Available > 3)
{
int id = peekStream.ReadInt16();
length = peekStream.ReadInt16();
byte[] data = peekStream.ReadBytes(length);
extensions.Add(SslExtensions.GetExtension(id, data));
}
}
}
}
var serverHelloInfo = new ServerHelloInfo var serverHelloInfo = new ServerHelloInfo
{ {
...@@ -278,8 +244,8 @@ namespace Titanium.Web.Proxy.Ssl ...@@ -278,8 +244,8 @@ namespace Titanium.Web.Proxy.Ssl
MinorVersion = minorVersion, MinorVersion = minorVersion,
Random = random, Random = random,
SessionId = sessionId, SessionId = sessionId,
Ciphers = ciphers, CipherSuite = cipherSuite,
CompressionData = compressionData, CompressionMethod = compressionMethod,
Extensions = extensions, Extensions = extensions,
}; };
......
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