Commit 680f8c01 authored by justcoding121's avatar justcoding121

Use StreamExtended library

parent 5bce0485
using System.IO; using StreamExtended.Helpers;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
......
using System.IO; using StreamExtended.Helpers;
using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
......
using System; using StreamExtended.Network;
using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
......
using System.Collections.Concurrent;
namespace Titanium.Web.Proxy.Helpers
{
internal static class BufferPool
{
private static readonly ConcurrentQueue<byte[]> buffers = new ConcurrentQueue<byte[]>();
internal static byte[] GetBuffer(int bufferSize)
{
byte[] buffer;
if (!buffers.TryDequeue(out buffer) || buffer.Length != bufferSize)
{
buffer = new byte[bufferSize];
}
return buffer;
}
internal static void ReturnBuffer(byte[] buffer)
{
if (buffer != null)
{
buffers.Enqueue(buffer);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
/// <summary>
/// A custom binary reader that would allo us to read string line by line
/// using the specified encoding
/// as well as to read bytes as required
/// </summary>
internal class CustomBinaryReader : IDisposable
{
private readonly CustomBufferedStream stream;
private readonly Encoding encoding;
private bool disposed;
internal byte[] Buffer { get; }
internal CustomBinaryReader(CustomBufferedStream stream, int bufferSize)
{
this.stream = stream;
Buffer = BufferPool.GetBuffer(bufferSize);
//default to UTF-8
encoding = Encoding.UTF8;
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
internal async Task<string> ReadLineAsync()
{
byte lastChar = default(byte);
int bufferDataLength = 0;
// try to use the thread static buffer, usually it is enough
var buffer = Buffer;
while (stream.DataAvailable || await stream.FillBufferAsync())
{
byte newChar = stream.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
//if new line
if (lastChar == '\r' && newChar == '\n')
{
return encoding.GetString(buffer, 0, bufferDataLength - 1);
}
//end of stream
if (newChar == '\0')
{
return encoding.GetString(buffer, 0, bufferDataLength);
}
bufferDataLength++;
//store last char for new line comparison
lastChar = newChar;
if (bufferDataLength == buffer.Length)
{
ResizeBuffer(ref buffer, bufferDataLength * 2);
}
}
if (bufferDataLength == 0)
{
return null;
}
return encoding.GetString(buffer, 0, bufferDataLength);
}
/// <summary>
/// Read until the last new line
/// </summary>
/// <returns></returns>
internal async Task<List<string>> ReadAllLinesAsync()
{
string tmpLine;
var requestLines = new List<string>();
while (!string.IsNullOrEmpty(tmpLine = await ReadLineAsync()))
{
requestLines.Add(tmpLine);
}
return requestLines;
}
/// <summary>
/// Read until the last new line, ignores the result
/// </summary>
/// <returns></returns>
internal async Task ReadAndIgnoreAllLinesAsync()
{
while (!string.IsNullOrEmpty(await ReadLineAsync()))
{
}
}
/// <summary>
/// Read the specified number (or less) of raw bytes from the base stream to the given buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="bytesToRead"></param>
/// <returns>The number of bytes read</returns>
internal Task<int> ReadBytesAsync(byte[] buffer, int bytesToRead)
{
return stream.ReadAsync(buffer, 0, bytesToRead);
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
BufferPool.ReturnBuffer(Buffer);
}
}
/// <summary>
/// Increase size of buffer and copy existing content to new buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="size"></param>
private void ResizeBuffer(ref byte[] buffer, long size)
{
var newBuffer = new byte[size];
System.Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer;
}
}
}
using System.Threading.Tasks;
namespace Titanium.Web.Proxy.Helpers
{
class CustomBufferedPeekStream
{
private readonly CustomBufferedStream baseStream;
public int Position { get; private set; }
public CustomBufferedPeekStream(CustomBufferedStream baseStream, int startPosition = 0)
{
this.baseStream = baseStream;
Position = startPosition;
}
public int Available => baseStream.Available - Position;
public async Task<bool> EnsureBufferLength(int length)
{
var val = await baseStream.PeekByteAsync(Position + 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;
}
}
}
This diff is collapsed.
using System; using StreamExtended.Network;
using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
......
using Titanium.Web.Proxy.Ssl; using StreamExtended;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
......
using System; using StreamExtended;
using System;
using System.Net; using System.Net;
using Titanium.Web.Proxy.Ssl;
namespace Titanium.Web.Proxy.Http namespace Titanium.Web.Proxy.Http
{ {
......
using System.Collections.Generic; using StreamExtended.Network;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
......
using System.Net.Sockets; using StreamExtended.Network;
using System.Net.Sockets;
using Titanium.Web.Proxy.Helpers; using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Network namespace Titanium.Web.Proxy.Network
......
using System.Diagnostics;
using System.IO;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Ssl;
namespace Titanium.Web.Proxy.Network.Tcp
{
internal class ClientHelloAlpnAdderStream : Stream
{
private readonly CustomBufferedStream stream;
private bool called;
public ClientHelloAlpnAdderStream(CustomBufferedStream stream)
{
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 clientHello = SslTools.GetClientHelloInfo(new CustomBufferedStream(ms, (int)ms.Length)).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 { return stream.Position; }
set { stream.Position = value; }
}
}
}
\ No newline at end of file
using System.Diagnostics;
using System.IO;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Ssl;
namespace Titanium.Web.Proxy.Network.Tcp
{
internal class ServerHelloAlpnAdderStream : Stream
{
private readonly CustomBufferedStream stream;
private bool called;
public ServerHelloAlpnAdderStream(CustomBufferedStream stream)
{
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 serverHello = SslTools.GetServerHelloInfo(new CustomBufferedStream(ms, (int)ms.Length)).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 { return stream.Position; }
set { stream.Position = value; }
}
}
}
\ No newline at end of file
using System; using StreamExtended.Network;
using System.IO; using System;
using System.Net.Sockets; using System.Net.Sockets;
using Titanium.Web.Proxy.Extensions; using Titanium.Web.Proxy.Extensions;
using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Models; using Titanium.Web.Proxy.Models;
namespace Titanium.Web.Proxy.Network.Tcp namespace Titanium.Web.Proxy.Network.Tcp
......
using System; using StreamExtended.Network;
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Security; using System.Net.Security;
......
using System; using StreamExtended.Network;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
......
using System; using StreamExtended;
using StreamExtended.Network;
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
...@@ -13,7 +15,6 @@ using Titanium.Web.Proxy.Helpers; ...@@ -13,7 +15,6 @@ using Titanium.Web.Proxy.Helpers;
using Titanium.Web.Proxy.Http; 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.Ssl;
namespace Titanium.Web.Proxy namespace Titanium.Web.Proxy
{ {
......
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; }
internal int ClientHelloLength { get; set; }
internal int EntensionsStartPosition { 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 compressionMethod = CompressionData[0];
string compression = compressions.Length > compressionMethod
? compressions[compressionMethod]
: $"unknown [0x{compressionMethod:X2}]";
sb.AppendLine($"Compression: {compression}");
}
if (Ciphers.Length > 0)
{
sb.AppendLine("Ciphers:");
foreach (int cipherSuite in Ciphers)
{
string cipherStr;
if (!SslCiphers.Ciphers.TryGetValue(cipherSuite, out cipherStr))
{
cipherStr = "unknown";
}
sb.AppendLine($"[0x{cipherSuite:X4}] {cipherStr}");
}
}
return sb.ToString();
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Titanium.Web.Proxy.Ssl
{
public class ServerHelloInfo
{
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 CipherSuite { get; set; }
public byte CompressionMethod { get; set; }
internal int ServerHelloLength { get; set; }
internal int EntensionsStartPosition { 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 ServerHello 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}");
}
}
string compression = compressions.Length > CompressionMethod
? compressions[CompressionMethod]
: $"unknown [0x{CompressionMethod:X2}]";
sb.AppendLine($"Compression: {compression}");
sb.Append("Cipher:");
string cipherStr;
if (!SslCiphers.Ciphers.TryGetValue(CipherSuite, out cipherStr))
{
cipherStr = "unknown";
}
sb.AppendLine($"[0x{CipherSuite: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
This diff is collapsed.
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Titanium.Web.Proxy.Helpers;
namespace Titanium.Web.Proxy.Ssl
{
class SslTools
{
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);
int extenstionsStartPosition = peekStream.Position;
var extensions = await ReadExtensions(majorVersion, minorVersion, peekStream);
//var rawBytes = new CustomBufferedPeekStream(clientStream).ReadBytes(peekStream.Position);
var clientHelloInfo = new ClientHelloInfo
{
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
Ciphers = ciphers,
CompressionData = compressionData,
ClientHelloLength = peekStream.Position,
EntensionsStartPosition = extenstionsStartPosition,
Extensions = extensions,
};
return clientHelloInfo;
}
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)
{
var serverHello = await GetServerHelloInfo(serverStream);
return serverHello != null;
}
public static async Task<ServerHelloInfo> GetServerHelloInfo(CustomBufferedStream serverStream)
{
//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 serverStream.PeekByteAsync(0);
if (recordType == 0x80)
{
// copied from client hello, not tested. SSL2 is deprecated
var peekStream = new CustomBufferedPeekStream(serverStream, 1);
//SSL 2
int length = peekStream.ReadByte();
if (length < 9)
{
// Message body too short.
return null;
}
if (peekStream.ReadByte() != 0x02)
{
// should be ServerHello
return null;
}
int majorVersion = serverStream.ReadByte();
int minorVersion = serverStream.ReadByte();
return new ServerHelloInfo();
}
else if (recordType == 0x16)
{
var peekStream = new CustomBufferedPeekStream(serverStream, 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() != 0x02)
{
// should be ServerHello
return null;
}
length = peekStream.ReadInt24();
majorVersion = peekStream.ReadByte();
minorVersion = peekStream.ReadByte();
byte[] random = peekStream.ReadBytes(32);
length = peekStream.ReadByte();
// sessionid + cipherSuite + compressionMethod
if (!await peekStream.EnsureBufferLength(length + 2 + 1))
{
return null;
}
byte[] sessionId = peekStream.ReadBytes(length);
int cipherSuite = peekStream.ReadInt16();
byte compressionMethod = peekStream.ReadByte();
int extenstionsStartPosition = peekStream.Position;
var extensions = await ReadExtensions(majorVersion, minorVersion, peekStream);
//var rawBytes = new CustomBufferedPeekStream(serverStream).ReadBytes(peekStream.Position);
var serverHelloInfo = new ServerHelloInfo
{
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
CipherSuite = cipherSuite,
CompressionMethod = compressionMethod,
ServerHelloLength = peekStream.Position,
EntensionsStartPosition = extenstionsStartPosition,
Extensions = extensions,
};
return serverHelloInfo;
}
return null;
}
}
}
...@@ -50,6 +50,10 @@ ...@@ -50,6 +50,10 @@
<HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath> <HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="StreamExtended, Version=1.0.13.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\packages\StreamExtended.1.0.13\lib\net45\StreamExtended.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" /> <Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
...@@ -87,10 +91,6 @@ ...@@ -87,10 +91,6 @@
<Compile Include="Extensions\StreamExtensions.cs" /> <Compile Include="Extensions\StreamExtensions.cs" />
<Compile Include="Extensions\StringExtensions.cs" /> <Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="Extensions\TcpExtensions.cs" /> <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\Firefox.cs" />
<Compile Include="Helpers\HttpRequestWriter.cs" /> <Compile Include="Helpers\HttpRequestWriter.cs" />
<Compile Include="Helpers\HttpResponseWriter.cs" /> <Compile Include="Helpers\HttpResponseWriter.cs" />
...@@ -98,18 +98,10 @@ ...@@ -98,18 +98,10 @@
<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="Network\Tcp\ServerHelloAlpnAdderStream.cs" />
<Compile Include="Network\Tcp\ClientHelloAlpnAdderStream.cs" />
<Compile Include="Ssl\ServerHelloInfo.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="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" />
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
<tags></tags> <tags></tags>
<dependencies> <dependencies>
<dependency id="BouncyCastle" version="1.8.1" /> <dependency id="BouncyCastle" version="1.8.1" />
<dependency id="StreamExtended" version="1.0.13" />
</dependencies> </dependencies>
</metadata> </metadata>
<files> <files>
......
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="BouncyCastle" version="1.8.1" targetFramework="net45" /> <package id="BouncyCastle" version="1.8.1" targetFramework="net45" />
<package id="StreamExtended" version="1.0.13" targetFramework="net45" />
</packages> </packages>
\ No newline at end of file
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