Commit 70e1a0dd authored by Honfika's avatar Honfika

Parse ciphers

parent 1589642a
using System; using System.Threading.Tasks;
using System.Collections.Generic; using Titanium.Web.Proxy.Ssl;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
...@@ -10,81 +7,4 @@ namespace Titanium.Web.Proxy.Http ...@@ -10,81 +7,4 @@ namespace Titanium.Web.Proxy.Http
{ {
public ClientHelloInfo ClientHelloInfo { get; set; } 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; }
public byte[] CiphersData { get; set; }
public byte[] CompressionData { get; set; }
public List<Extension> Extensions { 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")))}");
if (Extensions != null)
{
sb.AppendLine("Extensions:");
foreach (var extension in Extensions)
{
sb.AppendLine($"{extension.Name}: {extension.Data}");
}
}
return sb.ToString();
}
}
} }
...@@ -17,6 +17,7 @@ using Titanium.Web.Proxy.Http; ...@@ -17,6 +17,7 @@ using Titanium.Web.Proxy.Http;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
using Titanium.Web.Proxy.Network.Tcp; using Titanium.Web.Proxy.Network.Tcp;
using Titanium.Web.Proxy.Shared; using Titanium.Web.Proxy.Shared;
using Titanium.Web.Proxy.Ssl;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
...@@ -116,7 +117,12 @@ namespace Titanium.Web.Proxy ...@@ -116,7 +117,12 @@ 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, connectArgs); var clientHelloInfo = await HttpsTools.GetClientHelloInfo(clientStream);
bool isClientHello = clientHelloInfo != null;
if (isClientHello)
{
connectRequest.ClientHelloInfo = clientHelloInfo;
}
if (TunnelConnectResponse != null) if (TunnelConnectResponse != null)
{ {
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Titanium.Web.Proxy.Ssl
{
public class ClientHelloInfo
{
private static readonly string[] compressions = {
"null",
"DEFLATE"
};
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; }
public int[] Ciphers { get; set; }
public byte[] CompressionData { get; set; }
public List<SslExtension> Extensions { 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")))}");
if (Extensions != null)
{
sb.AppendLine("Extensions:");
foreach (var extension in Extensions)
{
sb.AppendLine($"{extension.Name}: {extension.Data}");
}
}
if (CompressionData.Length > 0)
{
int id = CompressionData[0];
string compression = null;
compression = compressions.Length > id ? compressions[id] : $"unknown [0x{id:X2}]";
sb.AppendLine($"Compression: {compression}");
}
if (Ciphers.Length > 0)
{
sb.AppendLine($"Ciphers:");
foreach (int cipher in Ciphers)
{
string cipherStr;
if (!SslCiphers.Ciphers.TryGetValue(cipher, out cipherStr))
{
cipherStr = $"unknown";
}
sb.AppendLine($"[0x{cipher:X4}] {cipherStr}");
}
}
return sb.ToString();
}
}
}
\ No newline at end of file
This diff is collapsed.
namespace Titanium.Web.Proxy.Ssl
{
public class SslExtension
{
public int Value { get; set; }
public string Name { get; set; }
public string Data { get; set; }
public SslExtension(int value, string name, string data)
{
Value = value;
Name = name;
Data = data;
}
}
}
\ No newline at end of file
...@@ -3,149 +3,16 @@ using System.Collections.Generic; ...@@ -3,149 +3,16 @@ 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;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Ssl
{ {
class HttpsTools internal class SslExtensions
{ {
public static async Task<bool> IsClientHello(CustomBufferedStream clientStream, TunnelConnectSessionEventArgs connectArgs) internal static SslExtension GetExtension(int value, byte[] data)
{
//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 = peekStream.ReadByte();
if (length < 9)
{
// Message body too short.
return false;
}
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return false;
}
int majorVersion = clientStream.ReadByte();
int minorVersion = clientStream.ReadByte();
return true;
}
else if (recordType == 0x16)
{
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//should contain at least 43 bytes
// 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length
if (!await peekStream.EnsureBufferLength(43))
{
return false;
}
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
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();
// sessionid + 2 ciphersData length
if (!await peekStream.EnsureBufferLength(length + 2))
{
return false;
}
byte[] sessionId = peekStream.ReadBytes(length);
length = peekStream.ReadInt16();
// ciphersData + compressionData length
if (!await peekStream.EnsureBufferLength(length + 1))
{
return false;
}
byte[] ciphersData = peekStream.ReadBytes(length);
length = peekStream.ReadByte();
if (length < 1)
{
return false;
}
// compressionData
if (!await peekStream.EnsureBufferLength(length))
{
return false;
}
byte[] compressionData = peekStream.ReadBytes(length);
List<Extension> 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<Extension>();
while (peekStream.Available > 3)
{
int id = peekStream.ReadInt16();
length = peekStream.ReadInt16();
byte[] data = peekStream.ReadBytes(length);
extensions.Add(GetExtension(id, data));
}
}
}
}
request.ClientHelloInfo = new ClientHelloInfo
{
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
CiphersData = ciphersData,
CompressionData = compressionData,
Extensions = extensions,
};
return true;
}
return false;
}
private static Extension GetExtension(int value, byte[] data)
{ {
string name = GetExtensionName(value); string name = GetExtensionName(value);
string dataStr = GetExtensionData(value, data); string dataStr = GetExtensionData(value, data);
return new Extension(value, name, dataStr); return new SslExtension(value, name, dataStr);
} }
private static string GetExtensionData(int value, byte[] data) private static string GetExtensionData(int value, byte[] data)
...@@ -528,20 +395,4 @@ namespace Titanium.Web.Proxy.Http ...@@ -528,20 +395,4 @@ namespace Titanium.Web.Proxy.Http
return string.Join(" ", data.Select(x => x.ToString("X2"))); return string.Join(" ", data.Select(x => x.ToString("X2")));
} }
} }
public class Extension
{
public int Value { get; set; }
public string Name { get; set; }
public string Data { get; set; }
public Extension(int value, string name, string data)
{
Value = value;
Name = name;
Data = data;
}
}
} }
using System.Collections.Generic;
using System.Threading.Tasks;
using Titanium.Web.Proxy.EventArguments;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http;
namespace Titanium.Web.Proxy.Ssl
{
class HttpsTools
{
public static async Task<bool> IsClientHello(CustomBufferedStream clientStream)
{
var clientHello = await GetClientHelloInfo(clientStream);
return clientHello != null;
}
public static async Task<ClientHelloInfo> GetClientHelloInfo(CustomBufferedStream clientStream)
{
//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
int recordType = await clientStream.PeekByteAsync(0);
if (recordType == 0x80)
{
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//SSL 2
int length = peekStream.ReadByte();
if (length < 9)
{
// Message body too short.
return null;
}
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return null;
}
int majorVersion = clientStream.ReadByte();
int minorVersion = clientStream.ReadByte();
return new ClientHelloInfo();
}
else if (recordType == 0x16)
{
var peekStream = new CustomBufferedPeekStream(clientStream, 1);
//should contain at least 43 bytes
// 2 version + 2 length + 1 type + 3 length(?) + 2 version + 32 random + 1 sessionid length
if (!await peekStream.EnsureBufferLength(43))
{
return null;
}
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
int length = peekStream.ReadInt16();
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return null;
}
length = peekStream.ReadInt24();
majorVersion = peekStream.ReadByte();
minorVersion = peekStream.ReadByte();
byte[] random = peekStream.ReadBytes(32);
length = peekStream.ReadByte();
// sessionid + 2 ciphersData length
if (!await peekStream.EnsureBufferLength(length + 2))
{
return null;
}
byte[] sessionId = peekStream.ReadBytes(length);
length = peekStream.ReadInt16();
// ciphersData + compressionData length
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 clientHelloInfo = new ClientHelloInfo
{
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
Ciphers = ciphers,
CompressionData = compressionData,
Extensions = extensions,
};
return clientHelloInfo;
}
return null;
}
}
}
...@@ -95,11 +95,15 @@ ...@@ -95,11 +95,15 @@
<Compile Include="Helpers\HttpHelper.cs" /> <Compile Include="Helpers\HttpHelper.cs" />
<Compile Include="Helpers\Network.cs" /> <Compile Include="Helpers\Network.cs" />
<Compile Include="Helpers\Tcp.cs" /> <Compile Include="Helpers\Tcp.cs" />
<Compile Include="Ssl\ClientHelloInfo.cs" />
<Compile Include="Http\ConnectRequest.cs" /> <Compile Include="Http\ConnectRequest.cs" />
<Compile Include="Http\ConnectResponse.cs" /> <Compile Include="Http\ConnectResponse.cs" />
<Compile Include="Http\HeaderCollection.cs" /> <Compile Include="Http\HeaderCollection.cs" />
<Compile Include="Http\HeaderParser.cs" /> <Compile Include="Http\HeaderParser.cs" />
<Compile Include="Http\HttpsTools.cs" /> <Compile Include="Ssl\SslCiphers.cs" />
<Compile Include="Ssl\SslExtension.cs" />
<Compile Include="Ssl\SslExtensions.cs" />
<Compile Include="Ssl\SslTools.cs" />
<Compile Include="Http\HttpWebClient.cs" /> <Compile Include="Http\HttpWebClient.cs" />
<Compile Include="Http\Request.cs" /> <Compile Include="Http\Request.cs" />
<Compile Include="Http\Response.cs" /> <Compile Include="Http\Response.cs" />
...@@ -160,6 +164,7 @@ ...@@ -160,6 +164,7 @@
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="StrongNameKey.snk" /> <None Include="StrongNameKey.snk" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" /> <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
......
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