Commit fd3666d5 authored by justcoding121's avatar justcoding121 Committed by justcoding121

Merge pull request #287 from honfika/develop

Parse Ssl client hello
parents 7f5a1cef a2e87c24
...@@ -16,6 +16,7 @@ using System.Windows.Navigation; ...@@ -16,6 +16,7 @@ using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using Titanium.Web.Proxy.EventArguments; using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Examples.Wpf namespace Titanium.Web.Proxy.Examples.Wpf
...@@ -71,7 +72,10 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -71,7 +72,10 @@ namespace Titanium.Web.Proxy.Examples.Wpf
proxyServer.TrustRootCertificate = true; proxyServer.TrustRootCertificate = true;
proxyServer.ForwardToUpstreamGateway = true; proxyServer.ForwardToUpstreamGateway = true;
var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true); var explicitEndPoint = new ExplicitProxyEndPoint(IPAddress.Any, 8000, true)
{
//IncludedHttpsHostNameRegex = new string[0],
};
proxyServer.AddEndPoint(explicitEndPoint); proxyServer.AddEndPoint(explicitEndPoint);
proxyServer.BeforeRequest += ProxyServer_BeforeRequest; proxyServer.BeforeRequest += ProxyServer_BeforeRequest;
proxyServer.BeforeResponse += ProxyServer_BeforeResponse; proxyServer.BeforeResponse += ProxyServer_BeforeResponse;
...@@ -101,10 +105,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -101,10 +105,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item; SessionListItem item;
if (sessionDictionary.TryGetValue(e, out item)) if (sessionDictionary.TryGetValue(e, out item))
{ {
item.Response.ResponseStatusCode = e.WebSession.Response.ResponseStatusCode; item.Response = e.WebSession.Response;
item.Response.ResponseStatusDescription = e.WebSession.Response.ResponseStatusDescription;
item.Response.HttpVersion = e.WebSession.Response.HttpVersion;
item.Response.ResponseHeaders.AddHeaders(e.WebSession.Response.ResponseHeaders);
item.Update(); item.Update();
} }
}); });
...@@ -132,10 +133,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -132,10 +133,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item2; SessionListItem item2;
if (sessionDictionary.TryGetValue(e, out item2)) if (sessionDictionary.TryGetValue(e, out item2))
{ {
item2.Response.ResponseStatusCode = e.WebSession.Response.ResponseStatusCode; item2.Response = e.WebSession.Response;
item2.Response.ResponseStatusDescription = e.WebSession.Response.ResponseStatusDescription;
item2.Response.HttpVersion = e.WebSession.Response.HttpVersion;
item2.Response.ResponseHeaders.AddHeaders(e.WebSession.Response.ResponseHeaders);
item2.Update(); item2.Update();
item = item2; item = item2;
} }
...@@ -165,16 +163,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -165,16 +163,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{ {
Number = lastSessionNumber, Number = lastSessionNumber,
SessionArgs = e, SessionArgs = e,
Request = // save the headers because TWP will set it to null in Dispose
{ RequestHeaders = e.WebSession.Request.RequestHeaders,
Method = e.WebSession.Request.Method, ResponseHeaders = e.WebSession.Response.ResponseHeaders,
RequestUri = e.WebSession.Request.RequestUri, Request = e.WebSession.Request,
HttpVersion = e.WebSession.Request.HttpVersion, Response = e.WebSession.Response,
},
}; };
item.Request.RequestHeaders.AddHeaders(e.WebSession.Request.RequestHeaders);
if (e is TunnelConnectSessionEventArgs || e.WebSession.Request.UpgradeToWebSocket) if (e is TunnelConnectSessionEventArgs || e.WebSession.Request.UpgradeToWebSocket)
{ {
e.DataReceived += (sender, args) => e.DataReceived += (sender, args) =>
...@@ -232,9 +227,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -232,9 +227,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf
data = data.Take(truncateLimit).ToArray(); data = data.Take(truncateLimit).ToArray();
} }
//restore the headers
typeof(Request).GetProperty(nameof(Request.RequestHeaders)).SetValue(session.Request, session.RequestHeaders);
typeof(Response).GetProperty(nameof(Response.ResponseHeaders)).SetValue(session.Response, session.ResponseHeaders);
//string hexStr = string.Join(" ", data.Select(x => x.ToString("X2"))); //string hexStr = string.Join(" ", data.Select(x => x.ToString("X2")));
TextBoxRequest.Text = session.Request.HeaderText + session.Request.Encoding.GetString(data) + TextBoxRequest.Text = session.Request.HeaderText + session.Request.Encoding.GetString(data) +
(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null); (truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null) +
(session.Request as ConnectRequest)?.ClientHelloInfo;
data = session.ResponseBody ?? new byte[0]; data = session.ResponseBody ?? new byte[0];
truncated = data.Length > truncateLimit; truncated = data.Length > truncateLimit;
...@@ -245,7 +245,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -245,7 +245,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf
//hexStr = string.Join(" ", data.Select(x => x.ToString("X2"))); //hexStr = string.Join(" ", data.Select(x => x.ToString("X2")));
TextBoxResponse.Text = session.Response.HeaderText + session.Response.Encoding.GetString(data) + TextBoxResponse.Text = session.Response.HeaderText + session.Response.Encoding.GetString(data) +
(truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null); (truncated ? Environment.NewLine + $"Data is truncated after {truncateLimit} bytes" : null) +
(session.Response as ConnectResponse)?.ServerHelloInfo;
} }
} }
} }
...@@ -78,9 +78,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf ...@@ -78,9 +78,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf
public byte[] ResponseBody { get; set; } public byte[] ResponseBody { get; set; }
public Request Request { get; set; } = new Request(); public Request Request { get; set; }
public Response Response { get; set; } = new Response(); public Response Response { get; set; }
public HeaderCollection RequestHeaders { get; set; }
public HeaderCollection ResponseHeaders { get; set; }
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
class CustomBufferedPeekStream
{
private readonly CustomBufferedStream baseStream;
private int position;
public CustomBufferedPeekStream(CustomBufferedStream baseStream, int startPosition = 0)
{
this.baseStream = baseStream;
position = startPosition;
}
public async Task<bool> EnsureBufferLength(int length)
{
var val = await baseStream.PeekByteAsync(length - 1);
return val != -1;
}
public byte ReadByte()
{
return baseStream.PeekByteFromBuffer(position++);
}
public int ReadInt16()
{
int i1 = ReadByte();
int i2 = ReadByte();
return (i1 << 8) + i2;
}
public int ReadInt24()
{
int i1 = ReadByte();
int i2 = ReadByte();
int i3 = ReadByte();
return (i1 << 16) + (i2 << 8) + i3;
}
public byte[] ReadBytes(int length)
{
var buffer = new byte[length];
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = ReadByte();
}
return buffer;
}
}
}
...@@ -305,6 +305,16 @@ namespace Titanium.Web.Proxy.Helpers ...@@ -305,6 +305,16 @@ namespace Titanium.Web.Proxy.Helpers
return streamBuffer[bufferPos + index]; return streamBuffer[bufferPos + index];
} }
public byte PeekByteFromBuffer(int index)
{
if (bufferLength <= index)
{
throw new Exception("Index is out of buffer size");
}
return streamBuffer[bufferPos + index];
}
public byte ReadByteFromBuffer() public byte ReadByteFromBuffer()
{ {
if (bufferLength == 0) if (bufferLength == 0)
......
...@@ -8,5 +8,68 @@ namespace Titanium.Web.Proxy.Http ...@@ -8,5 +8,68 @@ namespace Titanium.Web.Proxy.Http
{ {
public class ConnectRequest : Request public class ConnectRequest : Request
{ {
public ClientHelloInfo ClientHelloInfo { get; set; }
}
public class ClientHelloInfo
{
public int MajorVersion { get; set; }
public int MinorVersion { get; set; }
public byte[] Random { get; set; }
public DateTime Time
{
get
{
DateTime time = DateTime.MinValue;
if (Random.Length > 3)
{
time = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)
.AddSeconds(((uint)Random[3] << 24) + ((uint)Random[2] << 16) + ((uint)Random[1] << 8) + (uint)Random[0]).ToLocalTime();
}
return time;
}
}
public byte[] SessionId { get; set; }
private static string SslVersionToString(int major, int minor)
{
string str = "Unknown";
if (major == 3 && minor == 3)
str = "TLS/1.2";
else if (major == 3 && minor == 2)
str = "TLS/1.1";
else if (major == 3 && minor == 1)
str = "TLS/1.0";
else if (major == 3 && minor == 0)
str = "SSL/3.0";
else if (major == 2 && minor == 0)
str = "SSL/2.0";
return $"{major}.{minor} ({str})";
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine("A SSLv3-compatible ClientHello handshake was found. Titanium extracted the parameters below.");
sb.AppendLine();
sb.AppendLine($"Version: {SslVersionToString(MajorVersion, MinorVersion)}");
sb.AppendLine($"Random: {string.Join(" ", Random.Select(x => x.ToString("X2")))}");
sb.AppendLine($"\"Time\": {Time}");
sb.AppendLine($"SessionID: {string.Join(" ", SessionId.Select(x => x.ToString("X2")))}");
return sb.ToString();
}
} }
} }
...@@ -8,5 +8,6 @@ namespace Titanium.Web.Proxy.Http ...@@ -8,5 +8,6 @@ namespace Titanium.Web.Proxy.Http
{ {
public class ConnectResponse : Response public class ConnectResponse : Response
{ {
public string ServerHelloInfo { get; set; }
} }
} }
...@@ -215,6 +215,15 @@ namespace Titanium.Web.Proxy.Http ...@@ -215,6 +215,15 @@ namespace Titanium.Web.Proxy.Http
return false; return false;
} }
/// <summary>
/// Removes all the headers.
/// </summary>
public void Clear()
{
Headers.Clear();
NonUniqueHeaders.Clear();
}
internal string GetHeaderValueOrNull(string headerName) internal string GetHeaderValueOrNull(string headerName)
{ {
HttpHeader header; HttpHeader header;
......
...@@ -3,54 +3,130 @@ using System.Collections.Generic; ...@@ -3,54 +3,130 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
class HttpsTools class HttpsTools
{ {
public static async Task<bool> IsClientHello(CustomBufferedStream clientStream) public static async Task<bool> IsClientHello(CustomBufferedStream clientStream, TunnelConnectSessionEventArgs connectArgs)
{ {
//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
var request = (ConnectRequest)connectArgs.WebSession.Request;
int recordType = await clientStream.PeekByteAsync(0); int recordType = await clientStream.PeekByteAsync(0);
if (recordType == 0x80) if (recordType == 0x80)
{ {
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//SSL 2 //SSL 2
int length = await clientStream.PeekByteAsync(1); int length = peekStream.ReadByte();
if (length < 9) if (length < 9)
{ {
// Message body too short. // Message body too short.
return false; return false;
} }
if (await clientStream.PeekByteAsync(2) != 0x01) if (peekStream.ReadByte() != 0x01)
{ {
// should be ClientHello // should be ClientHello
return false; return false;
} }
int majorVersion = await clientStream.PeekByteAsync(3); int majorVersion = clientStream.ReadByte();
int minorVersion = await clientStream.PeekByteAsync(4); int minorVersion = clientStream.ReadByte();
return true; return true;
} }
else if (recordType == 0x16) else if (recordType == 0x16)
{ {
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//should contain at least 43 bytes
int requiredLength = 43; // 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length
if (!await peekStream.EnsureBufferLength(requiredLength))
{
return false;
}
//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 = await clientStream.PeekByteAsync(1); int majorVersion = peekStream.ReadByte();
int minorVersion = await clientStream.PeekByteAsync(2); int minorVersion = peekStream.ReadByte();
int length1 = await clientStream.PeekByteAsync(3); int length = peekStream.ReadInt16();
int length2 = await clientStream.PeekByteAsync(4);
int length = (length1 << 8) + length2;
if (await clientStream.PeekByteAsync(5) != 0x01) if (peekStream.ReadByte() != 0x01)
{ {
// should be ClientHello // should be ClientHello
return false; return false;
} }
length = peekStream.ReadInt24();
majorVersion = peekStream.ReadByte();
minorVersion = peekStream.ReadByte();
byte[] random = peekStream.ReadBytes(32);
length = peekStream.ReadByte();
requiredLength += length + 2; // sessionid + 2 data length
if (!await peekStream.EnsureBufferLength(requiredLength))
{
return false;
}
byte[] sessionId = peekStream.ReadBytes(length);
length = peekStream.ReadInt16();
requiredLength += length + 1; // data + data2 length
if (!await peekStream.EnsureBufferLength(requiredLength))
{
return false;
}
byte[] data = peekStream.ReadBytes(length);
length = peekStream.ReadByte();
if (length < 1)
{
return false;
}
requiredLength += length; // data2
if (!await peekStream.EnsureBufferLength(requiredLength))
{
return false;
}
byte[] data2 = peekStream.ReadBytes(length);
byte[] data3 = null;
if (majorVersion > 3 || majorVersion == 3 && minorVersion >= 1)
{
requiredLength += 2;
if (await peekStream.EnsureBufferLength(requiredLength))
{
length = peekStream.ReadInt16();
requiredLength += length;
if (await peekStream.EnsureBufferLength(requiredLength))
{
data3 = peekStream.ReadBytes(length);
}
}
}
request.ClientHelloInfo = new ClientHelloInfo
{
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
};
return true; return true;
} }
......
...@@ -116,7 +116,7 @@ namespace Titanium.Web.Proxy ...@@ -116,7 +116,7 @@ namespace Titanium.Web.Proxy
connectArgs.WebSession.Response = CreateConnectResponse(version); connectArgs.WebSession.Response = CreateConnectResponse(version);
await WriteResponse(connectArgs.WebSession.Response, clientStreamWriter); await WriteResponse(connectArgs.WebSession.Response, clientStreamWriter);
bool isClientHello = await HttpsTools.IsClientHello(clientStream); bool isClientHello = await HttpsTools.IsClientHello(clientStream, connectArgs);
if (TunnelConnectResponse != null) if (TunnelConnectResponse != null)
{ {
......
...@@ -89,6 +89,7 @@ ...@@ -89,6 +89,7 @@
<Compile Include="Extensions\TcpExtensions.cs" /> <Compile Include="Extensions\TcpExtensions.cs" />
<Compile Include="Helpers\BufferPool.cs" /> <Compile Include="Helpers\BufferPool.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" /> <Compile Include="Helpers\CustomBinaryReader.cs" />
<Compile Include="Helpers\CustomBufferedPeekStream.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" /> <Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Firefox.cs" /> <Compile Include="Helpers\Firefox.cs" />
<Compile Include="Helpers\HttpHelper.cs" /> <Compile Include="Helpers\HttpHelper.cs" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment