Commit 122feb3b authored by Honfika's avatar Honfika

Parse Ssl client hello

parent 629ba957
......@@ -16,6 +16,7 @@ using System.Windows.Navigation;
using System.Windows.Shapes;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Examples.Wpf
......@@ -71,7 +72,10 @@ namespace Titanium.Web.Proxy.Examples.Wpf
proxyServer.TrustRootCertificate = 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.BeforeRequest += ProxyServer_BeforeRequest;
proxyServer.BeforeResponse += ProxyServer_BeforeResponse;
......@@ -101,10 +105,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item;
if (sessionDictionary.TryGetValue(e, out item))
{
item.Response.ResponseStatusCode = e.WebSession.Response.ResponseStatusCode;
item.Response.ResponseStatusDescription = e.WebSession.Response.ResponseStatusDescription;
item.Response.HttpVersion = e.WebSession.Response.HttpVersion;
item.Response.ResponseHeaders.AddHeaders(e.WebSession.Response.ResponseHeaders);
item.Response = e.WebSession.Response;
item.Update();
}
});
......@@ -132,10 +133,7 @@ namespace Titanium.Web.Proxy.Examples.Wpf
SessionListItem item2;
if (sessionDictionary.TryGetValue(e, out item2))
{
item2.Response.ResponseStatusCode = e.WebSession.Response.ResponseStatusCode;
item2.Response.ResponseStatusDescription = e.WebSession.Response.ResponseStatusDescription;
item2.Response.HttpVersion = e.WebSession.Response.HttpVersion;
item2.Response.ResponseHeaders.AddHeaders(e.WebSession.Response.ResponseHeaders);
item2.Response = e.WebSession.Response;
item2.Update();
item = item2;
}
......@@ -165,16 +163,13 @@ namespace Titanium.Web.Proxy.Examples.Wpf
{
Number = lastSessionNumber,
SessionArgs = e,
Request =
{
Method = e.WebSession.Request.Method,
RequestUri = e.WebSession.Request.RequestUri,
HttpVersion = e.WebSession.Request.HttpVersion,
},
// save the headers because TWP will set it to null in Dispose
RequestHeaders = e.WebSession.Request.RequestHeaders,
ResponseHeaders = e.WebSession.Response.ResponseHeaders,
Request = e.WebSession.Request,
Response = e.WebSession.Response,
};
item.Request.RequestHeaders.AddHeaders(e.WebSession.Request.RequestHeaders);
if (e is TunnelConnectSessionEventArgs || e.WebSession.Request.UpgradeToWebSocket)
{
e.DataReceived += (sender, args) =>
......@@ -232,9 +227,14 @@ namespace Titanium.Web.Proxy.Examples.Wpf
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")));
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];
truncated = data.Length > truncateLimit;
......@@ -245,7 +245,8 @@ namespace Titanium.Web.Proxy.Examples.Wpf
//hexStr = string.Join(" ", data.Select(x => x.ToString("X2")));
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
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;
......
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
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()
{
if (bufferLength == 0)
......
......@@ -8,5 +8,68 @@ namespace Titanium.Web.Proxy.Http
{
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
{
public class ConnectResponse : Response
{
public string ServerHelloInfo { get; set; }
}
}
......@@ -215,6 +215,15 @@ namespace Titanium.Web.Proxy.Http
return false;
}
/// <summary>
/// Removes all the headers.
/// </summary>
public void Clear()
{
Headers.Clear();
NonUniqueHeaders.Clear();
}
internal string GetHeaderValueOrNull(string headerName)
{
HttpHeader header;
......
......@@ -3,54 +3,130 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Http
{
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:
//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);
if (recordType == 0x80)
{
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//SSL 2
int length = await clientStream.PeekByteAsync(1);
int length = peekStream.ReadByte();
if (length < 9)
{
// Message body too short.
return false;
}
if (await clientStream.PeekByteAsync(2) != 0x01)
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return false;
}
int majorVersion = await clientStream.PeekByteAsync(3);
int minorVersion = await clientStream.PeekByteAsync(4);
int majorVersion = clientStream.ReadByte();
int minorVersion = clientStream.ReadByte();
return true;
}
else if (recordType == 0x16)
{
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = await clientStream.PeekByteAsync(1);
int minorVersion = await clientStream.PeekByteAsync(2);
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;
}
int length1 = await clientStream.PeekByteAsync(3);
int length2 = await clientStream.PeekByteAsync(4);
int length = (length1 << 8) + length2;
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
if (await clientStream.PeekByteAsync(5) != 0x01)
int length = peekStream.ReadInt16();
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
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;
}
......
......@@ -116,7 +116,7 @@ namespace Titanium.Web.Proxy
connectArgs.WebSession.Response = CreateConnectResponse(version);
await WriteResponse(connectArgs.WebSession.Response, clientStreamWriter);
bool isClientHello = await HttpsTools.IsClientHello(clientStream);
bool isClientHello = await HttpsTools.IsClientHello(clientStream, connectArgs);
if (TunnelConnectResponse != null)
{
......
......@@ -89,6 +89,7 @@
<Compile Include="Extensions\TcpExtensions.cs" />
<Compile Include="Helpers\BufferPool.cs" />
<Compile Include="Helpers\CustomBinaryReader.cs" />
<Compile Include="Helpers\CustomBufferedPeekStream.cs" />
<Compile Include="Helpers\CustomBufferedStream.cs" />
<Compile Include="Helpers\Firefox.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