Commit 72800159 authored by justcoding121's avatar justcoding121

import stream extended

parent 4e1f3931
......@@ -72,9 +72,6 @@
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="StreamExtended, Version=1.0.209.0, Culture=neutral, PublicKeyToken=bbfa0f1d54f50043, processorArchitecture=MSIL">
<HintPath>..\..\src\packages\StreamExtended.1.0.209-beta\lib\net45\StreamExtended.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
......@@ -128,7 +125,6 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
......@@ -145,6 +141,10 @@
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\StreamExtended\StreamExtended.csproj">
<Project>{61539dc1-ce80-41e6-a696-8f455ace8d15}</Project>
<Name>StreamExtended</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Titanium.Web.Proxy\Titanium.Web.Proxy.csproj">
<Project>{91018b6d-a7a9-45be-9cb3-79cbb8b169a6}</Project>
<Name>Titanium.Web.Proxy</Name>
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="StreamExtended" version="1.0.209-beta" targetFramework="net45" />
</packages>
\ No newline at end of file
using System.Collections.Concurrent;
namespace StreamExtended
{
/// <summary>
/// A concrete IBufferPool implementation using a thread-safe stack.
/// Works well when all consumers ask for buffers with the same size.
/// If your application would use variable size buffers consider implementing IBufferPool using System.Buffers library from Microsoft.
/// </summary>
public class DefaultBufferPool : IBufferPool
{
private readonly ConcurrentStack<byte[]> buffers = new ConcurrentStack<byte[]>();
/// <summary>
/// Gets a buffer.
/// </summary>
/// <param name="bufferSize">Size of the buffer.</param>
/// <returns></returns>
public byte[] GetBuffer(int bufferSize)
{
if (!buffers.TryPop(out var buffer) || buffer.Length != bufferSize)
{
buffer = new byte[bufferSize];
}
return buffer;
}
/// <summary>
/// Returns the buffer.
/// </summary>
/// <param name="buffer">The buffer.</param>
public void ReturnBuffer(byte[] buffer)
{
if (buffer != null)
{
buffers.Push(buffer);
}
}
public void Dispose()
{
buffers.Clear();
}
}
}
using System;
namespace StreamExtended
{
/// <summary>
/// Use this interface to implement custom buffer pool.
/// To use the default buffer pool implementation use DefaultBufferPool class.
/// </summary>
public interface IBufferPool : IDisposable
{
byte[] GetBuffer(int bufferSize);
void ReturnBuffer(byte[] buffer);
}
}
using StreamExtended.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StreamExtended
{
/// <summary>
/// Wraps up the client SSL hello information.
/// </summary>
public class ClientHelloInfo
{
private static readonly string[] compressions = {
"null",
"DEFLATE"
};
public int HandshakeVersion { get; set; }
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 Dictionary<string, 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 SSLv{HandshakeVersion}-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.Values.OrderBy(x => x.Position))
{
sb.AppendLine($"{extension.Name}: {extension.Data}");
}
}
if (CompressionData != null && 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)
{
if (!SslCiphers.Ciphers.TryGetValue(cipherSuite, out string cipherStr))
{
cipherStr = "unknown";
}
sb.AppendLine($"[0x{cipherSuite:X4}] {cipherStr}");
}
}
return sb.ToString();
}
}
}
\ No newline at end of file
using System.Collections.Generic;
namespace StreamExtended.Models
{
internal static class SslCiphers
{
internal static readonly Dictionary<int, string> Ciphers = new Dictionary<int, string>
{
{ 0x0000, "TLS_NULL_WITH_NULL_NULL" },
{ 0x0001, "TLS_RSA_WITH_NULL_MD5" },
{ 0x0002, "TLS_RSA_WITH_NULL_SHA" },
{ 0x0003, "TLS_RSA_EXPORT_WITH_RC4_40_MD5" },
{ 0x0004, "TLS_RSA_WITH_RC4_128_MD5" },
{ 0x0005, "TLS_RSA_WITH_RC4_128_SHA" },
{ 0x0006, "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5" },
{ 0x0007, "TLS_RSA_WITH_IDEA_CBC_SHA" },
{ 0x0008, "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0009, "TLS_RSA_WITH_DES_CBC_SHA" },
{ 0x000A, "TLS_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x000B, "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x000C, "TLS_DH_DSS_WITH_DES_CBC_SHA" },
{ 0x000D, "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0x000E, "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x000F, "TLS_DH_RSA_WITH_DES_CBC_SHA" },
{ 0x0010, "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x0011, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0012, "TLS_DHE_DSS_WITH_DES_CBC_SHA" },
{ 0x0013, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0x0014, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x0015, "TLS_DHE_RSA_WITH_DES_CBC_SHA" },
{ 0x0016, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x0017, "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5" },
{ 0x0018, "TLS_DH_anon_WITH_RC4_128_MD5" },
{ 0x0019, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA" },
{ 0x001A, "TLS_DH_anon_WITH_DES_CBC_SHA" },
{ 0x001B, "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA" },
{ 0x001C, "SSL_FORTEZZA_KEA_WITH_NULL_SHA" },
{ 0x001D, "SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA" },
//{ 0x001E, "SSL_FORTEZZA_KEA_WITH_RC4_128_SHA" },
// RFC 2712
{ 0x001E, "TLS_KRB5_WITH_DES_CBC_SHA" },
{ 0x001F, "TLS_KRB5_WITH_3DES_EDE_CBC_SHA" },
{ 0x0020, "TLS_KRB5_WITH_RC4_128_SHA" },
{ 0x0021, "TLS_KRB5_WITH_IDEA_CBC_SHA" },
{ 0x0022, "TLS_KRB5_WITH_DES_CBC_MD5" },
{ 0x0023, "TLS_KRB5_WITH_3DES_EDE_CBC_MD5" },
{ 0x0024, "TLS_KRB5_WITH_RC4_128_MD5" },
{ 0x0025, "TLS_KRB5_WITH_IDEA_CBC_MD5" },
{ 0x0026, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA" },
{ 0x0027, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA" },
{ 0x0028, "TLS_KRB5_EXPORT_WITH_RC4_40_SHA" },
{ 0x0029, "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5" },
{ 0x002A, "TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5" },
{ 0x002B, "TLS_KRB5_EXPORT_WITH_RC4_40_MD5" },
// RFC 4785
{ 0x002C, "TLS_PSK_WITH_NULL_SHA" },
{ 0x002D, "TLS_DHE_PSK_WITH_NULL_SHA" },
{ 0x002E, "TLS_RSA_PSK_WITH_NULL_SHA" },
// RFC 5246
{ 0x002F, "TLS_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0030, "TLS_DH_DSS_WITH_AES_128_CBC_SHA" },
{ 0x0031, "TLS_DH_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0032, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA" },
{ 0x0033, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA" },
{ 0x0034, "TLS_DH_anon_WITH_AES_128_CBC_SHA" },
{ 0x0035, "TLS_RSA_WITH_AES_256_CBC_SHA" },
{ 0x0036, "TLS_DH_DSS_WITH_AES_256_CBC_SHA" },
{ 0x0037, "TLS_DH_RSA_WITH_AES_256_CBC_SHA" },
{ 0x0038, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA" },
{ 0x0039, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA" },
{ 0x003A, "TLS_DH_anon_WITH_AES_256_CBC_SHA" },
{ 0x003B, "TLS_RSA_WITH_NULL_SHA256" },
{ 0x003C, "TLS_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x003D, "TLS_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x003E, "TLS_DH_DSS_WITH_AES_128_CBC_SHA256" },
{ 0x003F, "TLS_DH_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x0040, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256" },
{ 0x0041, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0042, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0043, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0044, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0045, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0046, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA" },
{ 0x0047, "TLS_ECDH_ECDSA_WITH_NULL_SHA" },
{ 0x0048, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA" },
{ 0x0049, "TLS_ECDH_ECDSA_WITH_DES_CBC_SHA" },
{ 0x004A, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA" },
{ 0x004B, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA" },
{ 0x004C, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA" },
{ 0x0060, "TLS_RSA_EXPORT1024_WITH_RC4_56_MD5" },
{ 0x0061, "TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5" },
{ 0x0062, "TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA" },
{ 0x0063, "TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA" },
{ 0x0064, "TLS_RSA_EXPORT1024_WITH_RC4_56_SHA" },
{ 0x0065, "TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA" },
{ 0x0066, "TLS_DHE_DSS_WITH_RC4_128_SHA" },
{ 0x0067, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256" },
{ 0x0068, "TLS_DH_DSS_WITH_AES_256_CBC_SHA256" },
{ 0x0069, "TLS_DH_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x006A, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256" },
{ 0x006B, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256" },
{ 0x006C, "TLS_DH_anon_WITH_AES_128_CBC_SHA256" },
{ 0x006D, "TLS_DH_anon_WITH_AES_256_CBC_SHA256" },
{ 0x0084, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0085, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0086, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0087, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0088, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA" },
{ 0x0089, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA" },
// RFC 4279
{ 0x008A, "TLS_PSK_WITH_RC4_128_SHA" },
{ 0x008B, "TLS_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x008C, "TLS_PSK_WITH_AES_128_CBC_SHA" },
{ 0x008D, "TLS_PSK_WITH_AES_256_CBC_SHA" },
{ 0x008E, "TLS_DHE_PSK_WITH_RC4_128_SHA" },
{ 0x008F, "TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x0090, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA" },
{ 0x0091, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA" },
{ 0x0092, "TLS_RSA_PSK_WITH_RC4_128_SHA" },
{ 0x0093, "TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0x0094, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA" },
{ 0x0095, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA" },
// RFC 4162
{ 0x0096, "TLS_RSA_WITH_SEED_CBC_SHA" },
{ 0x0097, "TLS_DH_DSS_WITH_SEED_CBC_SHA" },
{ 0x0098, "TLS_DH_RSA_WITH_SEED_CBC_SHA" },
{ 0x0099, "TLS_DHE_DSS_WITH_SEED_CBC_SHA" },
{ 0x009A, "TLS_DHE_RSA_WITH_SEED_CBC_SHA" },
{ 0x009B, "TLS_DH_anon_WITH_SEED_CBC_SHA" },
// RFC 5288
{ 0x009C, "TLS_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x009D, "TLS_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x009E, "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x009F, "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x00A0, "TLS_DH_RSA_WITH_AES_128_GCM_SHA256" },
{ 0x00A1, "TLS_DH_RSA_WITH_AES_256_GCM_SHA384" },
{ 0x00A2, "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" },
{ 0x00A3, "TLS_DHE_DSS_WITH_AES_256_GCM_SHA384" },
{ 0x00A4, "TLS_DH_DSS_WITH_AES_128_GCM_SHA256" },
{ 0x00A5, "TLS_DH_DSS_WITH_AES_256_GCM_SHA384" },
{ 0x00A6, "TLS_DH_anon_WITH_AES_128_GCM_SHA256" },
{ 0x00A7, "TLS_DH_anon_WITH_AES_256_GCM_SHA384" },
// RFC 5487
{ 0x00A8, "TLS_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00A9, "TLS_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AA, "TLS_DHE_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00AB, "TLS_DHE_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AC, "TLS_RSA_PSK_WITH_AES_128_GCM_SHA256" },
{ 0x00AD, "TLS_RSA_PSK_WITH_AES_256_GCM_SHA384" },
{ 0x00AE, "TLS_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00AF, "TLS_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B0, "TLS_PSK_WITH_NULL_SHA256" },
{ 0x00B1, "TLS_PSK_WITH_NULL_SHA384" },
{ 0x00B2, "TLS_DHE_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00B3, "TLS_DHE_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B4, "TLS_DHE_PSK_WITH_NULL_SHA256" },
{ 0x00B5, "TLS_DHE_PSK_WITH_NULL_SHA384" },
{ 0x00B6, "TLS_RSA_PSK_WITH_AES_128_CBC_SHA256" },
{ 0x00B7, "TLS_RSA_PSK_WITH_AES_256_CBC_SHA384" },
{ 0x00B8, "TLS_RSA_PSK_WITH_NULL_SHA256" },
{ 0x00B9, "TLS_RSA_PSK_WITH_NULL_SHA384" },
// RFC 5932
{ 0x00BA, "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BB, "TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BC, "TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BD, "TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BE, "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00BF, "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0x00C0, "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C1, "TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C2, "TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C3, "TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C4, "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00C5, "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256" },
{ 0x00FF, "TLS_EMPTY_RENEGOTIATION_INFO_SCSV" },
{ 0x5600, "TLS_FALLBACK_SCSV" },
// RFC 4492
{ 0xC001, "TLS_ECDH_ECDSA_WITH_NULL_SHA" },
{ 0xC002, "TLS_ECDH_ECDSA_WITH_RC4_128_SHA" },
{ 0xC003, "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC004, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA" },
{ 0xC005, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA" },
{ 0xC006, "TLS_ECDHE_ECDSA_WITH_NULL_SHA" },
{ 0xC007, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA" },
{ 0xC008, "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC009, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA" },
{ 0xC00A, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA" },
{ 0xC00B, "TLS_ECDH_RSA_WITH_NULL_SHA" },
{ 0xC00C, "TLS_ECDH_RSA_WITH_RC4_128_SHA" },
{ 0xC00D, "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC00E, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC00F, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC010, "TLS_ECDHE_RSA_WITH_NULL_SHA" },
{ 0xC011, "TLS_ECDHE_RSA_WITH_RC4_128_SHA" },
{ 0xC012, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC013, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC014, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC015, "TLS_ECDH_anon_WITH_NULL_SHA" },
{ 0xC016, "TLS_ECDH_anon_WITH_RC4_128_SHA" },
{ 0xC017, "TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA" },
{ 0xC018, "TLS_ECDH_anon_WITH_AES_128_CBC_SHA" },
{ 0xC019, "TLS_ECDH_anon_WITH_AES_256_CBC_SHA" },
// RFC 5054
{ 0xC01A, "TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01B, "TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01C, "TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA" },
{ 0xC01D, "TLS_SRP_SHA_WITH_AES_128_CBC_SHA" },
{ 0xC01E, "TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA" },
{ 0xC01F, "TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA" },
{ 0xC020, "TLS_SRP_SHA_WITH_AES_256_CBC_SHA" },
{ 0xC021, "TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA" },
{ 0xC022, "TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA" },
// RFC 5589
{ 0xC023, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256" },
{ 0xC024, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384" },
{ 0xC025, "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256" },
{ 0xC026, "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384" },
{ 0xC027, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256" },
{ 0xC028, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384" },
{ 0xC029, "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256" },
{ 0xC02A, "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384" },
{ 0xC02B, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" },
{ 0xC02C, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" },
{ 0xC02D, "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256" },
{ 0xC02E, "TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384" },
{ 0xC02F, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" },
{ 0xC030, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384" },
{ 0xC031, "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256" },
{ 0xC032, "TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384" },
// RFC 5489
{ 0xC033, "TLS_ECDHE_PSK_WITH_RC4_128_SHA" },
{ 0xC034, "TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA" },
{ 0xC035, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA" },
{ 0xC036, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA" },
{ 0xC037, "TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256" },
{ 0xC038, "TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384" },
{ 0xC039, "TLS_ECDHE_PSK_WITH_NULL_SHA" },
{ 0xC03A, "TLS_ECDHE_PSK_WITH_NULL_SHA256" },
{ 0xC03B, "TLS_ECDHE_PSK_WITH_NULL_SHA384" },
{ 0xC03C, "TLS_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC03D, "TLS_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC03E, "TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256" },
{ 0xC03F, "TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384" },
{ 0xC040, "TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC041, "TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC042, "TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256" },
{ 0xC043, "TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384" },
{ 0xC044, "TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC045, "TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC046, "TLS_DH_anon_WITH_ARIA_128_CBC_SHA256" },
{ 0xC047, "TLS_DH_anon_WITH_ARIA_256_CBC_SHA384" },
{ 0xC048, "TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC049, "TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC04A, "TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC04B, "TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC04C, "TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC04D, "TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC04E, "TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256" },
{ 0xC04F, "TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384" },
{ 0xC050, "TLS_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC051, "TLS_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC052, "TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC053, "TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC054, "TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC055, "TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC056, "TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256" },
{ 0xC057, "TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384" },
{ 0xC058, "TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256" },
{ 0xC059, "TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384" },
{ 0xC05A, "TLS_DH_anon_WITH_ARIA_128_GCM_SHA256" },
{ 0xC05B, "TLS_DH_anon_WITH_ARIA_256_GCM_SHA384" },
{ 0xC05C, "TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC05D, "TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC05E, "TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC05F, "TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC060, "TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC061, "TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC062, "TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256" },
{ 0xC063, "TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384" },
{ 0xC064, "TLS_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC065, "TLS_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC066, "TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC067, "TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC068, "TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC069, "TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC06A, "TLS_PSK_WITH_ARIA_128_GCM_SHA256" },
{ 0xC06B, "TLS_PSK_WITH_ARIA_256_GCM_SHA384" },
{ 0xC06C, "TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256" },
{ 0xC06D, "TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384" },
{ 0xC06E, "TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256" },
{ 0xC06F, "TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384" },
{ 0xC070, "TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256" },
{ 0xC071, "TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384" },
{ 0xC072, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC073, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC074, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC075, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC076, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC077, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC078, "TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC079, "TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC07A, "TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07B, "TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC07C, "TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07D, "TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC07E, "TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC07F, "TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC080, "TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC081, "TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC082, "TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC083, "TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC084, "TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC085, "TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC086, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC087, "TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC088, "TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC089, "TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08A, "TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08B, "TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08C, "TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08D, "TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC08E, "TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC08F, "TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC090, "TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC091, "TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC092, "TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256" },
{ 0xC093, "TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384" },
{ 0xC094, "TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC095, "TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC096, "TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC097, "TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC098, "TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC099, "TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC09A, "TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256" },
{ 0xC09B, "TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384" },
{ 0xC09C, "TLS_RSA_WITH_AES_128_CCM" },
{ 0xC09D, "TLS_RSA_WITH_AES_256_CCM" },
{ 0xC09E, "TLS_DHE_RSA_WITH_AES_128_CCM" },
{ 0xC09F, "TLS_DHE_RSA_WITH_AES_256_CCM" },
{ 0xC0A0, "TLS_RSA_WITH_AES_128_CCM_8" },
{ 0xC0A1, "TLS_RSA_WITH_AES_256_CCM_8" },
{ 0xC0A2, "TLS_DHE_RSA_WITH_AES_128_CCM_8" },
{ 0xC0A3, "TLS_DHE_RSA_WITH_AES_256_CCM_8" },
{ 0xC0A4, "TLS_PSK_WITH_AES_128_CCM" },
{ 0xC0A5, "TLS_PSK_WITH_AES_256_CCM" },
{ 0xC0A6, "TLS_DHE_PSK_WITH_AES_128_CCM" },
{ 0xC0A7, "TLS_DHE_PSK_WITH_AES_256_CCM" },
{ 0xC0A8, "TLS_PSK_WITH_AES_128_CCM_8" },
{ 0xC0A9, "TLS_PSK_WITH_AES_256_CCM_8" },
{ 0xC0AA, "TLS_PSK_DHE_WITH_AES_128_CCM_8" },
{ 0xC0AB, "TLS_PSK_DHE_WITH_AES_256_CCM_8" },
{ 0xC0AC, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM" },
{ 0xC0AD, "TLS_ECDHE_ECDSA_WITH_AES_256_CCM" },
{ 0xC0AE, "TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8" },
{ 0xC0AF, "TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8" },
// old numbers used in the beginning http://tools.ietf.org/html/draft-agl-tls-chacha20poly1305
{ 0xCC13, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCC14, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCC15, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
// http://tools.ietf.org/html/draft-ietf-tls-chacha20-poly1305
{ 0xCCA8, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCA9, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAA, "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAB, "TLS_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAC, "TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAD, "TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256" },
{ 0xCCAE, "TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256" },
// http://tools.ietf.org/html/draft-josefsson-salsa20-tls
{ 0xE410, "TLS_RSA_WITH_ESTREAM_SALSA20_SHA1" },
{ 0xE411, "TLS_RSA_WITH_SALSA20_SHA1" },
{ 0xE412, "TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1" },
{ 0xE413, "TLS_ECDHE_RSA_WITH_SALSA20_SHA1" },
{ 0xE414, "TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1" },
{ 0xE415, "TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1" },
{ 0xE416, "TLS_PSK_WITH_ESTREAM_SALSA20_SHA1" },
{ 0xE417, "TLS_PSK_WITH_SALSA20_SHA1" },
{ 0xE418, "TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1" },
{ 0xE419, "TLS_ECDHE_PSK_WITH_SALSA20_SHA1" },
{ 0xE41A, "TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1" },
{ 0xE41B, "TLS_RSA_PSK_WITH_SALSA20_SHA1" },
{ 0xE41C, "TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1" },
{ 0xE41D, "TLS_DHE_PSK_WITH_SALSA20_SHA1" },
{ 0xE41E, "TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1" },
{ 0xE41F, "TLS_DHE_RSA_WITH_SALSA20_SHA1" },
// these from http://www.mozilla.org/projects/security/pki/nss/ssl/fips-ssl-ciphersuites.html
{ 0xFEFE, "SSL_RSA_FIPS_WITH_DES_CBC_SHA"},
{ 0xFEFF, "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA" },
{ 0xFFE0, "SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA" },
{ 0xFFE1, "SSL_RSA_FIPS_WITH_DES_CBC_SHA"},
// note that ciphersuites of {0x00????} are TLS cipher suites in
// a sslv2 client hello message; the ???? above is the two-byte
// tls cipher suite id
{ 0x010080, "SSL2_RC4_128_WITH_MD5" },
{ 0x020080, "SSL2_RC4_128_EXPORT40_WITH_MD5" },
{ 0x030080, "SSL2_RC2_128_CBC_WITH_MD5" },
{ 0x040080, "SSL2_RC2_128_CBC_EXPORT40_WITH_MD5" },
{ 0x050080, "SSL2_IDEA_128_CBC_WITH_MD5" },
{ 0x060040, "SSL2_DES_64_CBC_WITH_MD5" },
{ 0x0700C0, "SSL2_DES_192_EDE3_CBC_WITH_MD5" },
{ 0x080080, "SSL2_RC4_64_WITH_MD5" },
// Microsoft's old PCT protocol. These are from Eric Rescorla's book "SSL and TLS"
{ 0x800001, "PCT_SSL_CERT_TYPE | PCT1_CERT_X509" },
{ 0x800003, "PCT_SSL_CERT_TYPE | PCT1_CERT_X509_CHAIN" },
{ 0x810001, "PCT_SSL_HASH_TYPE | PCT1_HASH_MD5" },
{ 0x810003, "PCT_SSL_HASH_TYPE | PCT1_HASH_SHA" },
{ 0x820001, "PCT_SSL_EXCH_TYPE | PCT1_EXCH_RSA_PKCS1" },
{ 0x830004, "PCT_SSL_CIPHER_TYPE_1ST_HALF | PCT1_CIPHER_RC4" },
{ 0x842840, "PCT_SSL_CIPHER_TYPE_2ND_HALF | PCT1_ENC_BITS_40 | PCT1_MAC_BITS_128" },
{ 0x848040, "PCT_SSL_CIPHER_TYPE_2ND_HALF | PCT1_ENC_BITS_128 | PCT1_MAC_BITS_128" },
{ 0x8F8001, "PCT_SSL_COMPAT | PCT_VERSION_1" },
};
}
}
namespace StreamExtended.Models
{
/// <summary>
/// The SSL extension information.
/// </summary>
public class SslExtension
{
/// <summary>
/// Gets the value.
/// </summary>
/// <value>
/// The value.
/// </value>
public int Value { get; }
/// <summary>
/// Gets the name.
/// </summary>
/// <value>
/// The name.
/// </value>
public string Name { get; }
/// <summary>
/// Gets the data.
/// </summary>
/// <value>
/// The data.
/// </value>
public string Data { get; }
/// <summary>
/// Gets the position.
/// </summary>
/// <value>
/// The position.
/// </value>
public int Position { get; }
/// <summary>
/// Initializes a new instance of the <see cref="SslExtension"/> class.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="name">The name.</param>
/// <param name="data">The data.</param>
/// <param name="position">The position.</param>
public SslExtension(int value, string name, string data, int position)
{
Value = value;
Name = name;
Data = data;
Position = position;
}
}
}
\ No newline at end of file
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace StreamExtended.Network
{
public class ClientHelloAlpnAdderStream : Stream
{
private readonly CustomBufferedStream stream;
private readonly IBufferPool bufferPool;
private bool called;
public ClientHelloAlpnAdderStream(CustomBufferedStream stream, IBufferPool bufferPool)
{
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 cts = new CancellationTokenSource();
var clientHello = SslTools.PeekClientHello(new CustomBufferedStream(ms, bufferPool, (int)ms.Length), bufferPool, cts.Token).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 => stream.Position;
set => stream.Position = value;
}
}
}
\ No newline at end of file
using System;
using System.Threading;
using System.Threading.Tasks;
namespace StreamExtended.Network
{
/// <summary>
/// Copies the source stream to destination stream.
/// But this let users to peek and read the copying process.
/// </summary>
public class CopyStream : ICustomStreamReader, IDisposable
{
private readonly ICustomStreamReader reader;
private readonly ICustomStreamWriter writer;
private readonly IBufferPool bufferPool;
public int BufferSize { get; }
private int bufferLength;
private byte[] buffer;
private bool disposed;
public int Available => reader.Available;
public bool DataAvailable => reader.DataAvailable;
public long ReadBytes { get; private set; }
public CopyStream(ICustomStreamReader reader, ICustomStreamWriter writer, IBufferPool bufferPool, int bufferSize)
{
this.reader = reader;
this.writer = writer;
BufferSize = bufferSize;
buffer = bufferPool.GetBuffer(bufferSize);
}
public async Task<bool> FillBufferAsync(CancellationToken cancellationToken = default(CancellationToken))
{
await FlushAsync(cancellationToken);
return await reader.FillBufferAsync(cancellationToken);
}
public byte PeekByteFromBuffer(int index)
{
return reader.PeekByteFromBuffer(index);
}
public Task<int> PeekByteAsync(int index, CancellationToken cancellationToken = default(CancellationToken))
{
return reader.PeekByteAsync(index, cancellationToken);
}
public Task<byte[]> PeekBytesAsync(int index, int size, CancellationToken cancellationToken = default(CancellationToken))
{
return reader.PeekBytesAsync(index, size, cancellationToken);
}
public void Flush()
{
//send out the current data from from the buffer
if (bufferLength > 0)
{
writer.Write(buffer, 0, bufferLength);
bufferLength = 0;
}
}
public async Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
{
//send out the current data from from the buffer
if (bufferLength > 0)
{
await writer.WriteAsync(buffer, 0, bufferLength, cancellationToken);
bufferLength = 0;
}
}
public byte ReadByteFromBuffer()
{
byte b = reader.ReadByteFromBuffer();
buffer[bufferLength++] = b;
ReadBytes++;
return b;
}
public int Read(byte[] buffer, int offset, int count)
{
int result = reader.Read(buffer, offset, count);
if (result > 0)
{
if (bufferLength + result > BufferSize)
{
Flush();
}
Buffer.BlockCopy(buffer, offset, this.buffer, bufferLength, result);
bufferLength += result;
ReadBytes += result;
Flush();
}
return result;
}
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default(CancellationToken))
{
int result = await reader.ReadAsync(buffer, offset, count, cancellationToken);
if (result > 0)
{
if (bufferLength + result > BufferSize)
{
await FlushAsync(cancellationToken);
}
Buffer.BlockCopy(buffer, offset, this.buffer, bufferLength, result);
bufferLength += result;
ReadBytes += result;
await FlushAsync(cancellationToken);
}
return result;
}
public Task<string> ReadLineAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken);
}
public void Dispose()
{
if (!disposed)
{
disposed = true;
var b = buffer;
buffer = null;
bufferPool.ReturnBuffer(b);
}
}
}
}
using System.Threading;
using System.Threading.Tasks;
namespace StreamExtended.Network
{
internal class CustomBufferedPeekStream : ICustomStreamReader
{
private readonly IBufferPool bufferPool;
private readonly ICustomStreamReader baseStream;
internal int Position { get; private set; }
internal CustomBufferedPeekStream(ICustomStreamReader baseStream, IBufferPool bufferPool, int startPosition = 0)
{
this.bufferPool = bufferPool;
this.baseStream = baseStream;
Position = startPosition;
}
int ICustomStreamReader.BufferSize => baseStream.BufferSize;
/// <summary>
/// Gets a value indicating whether data is available.
/// </summary>
bool ICustomStreamReader.DataAvailable => Available > 0;
/// <summary>
/// Gets the available data size.
/// </summary>
public int Available => baseStream.Available - Position;
internal async Task<bool> EnsureBufferLength(int length, CancellationToken cancellationToken)
{
var val = await baseStream.PeekByteAsync(Position + length - 1, cancellationToken);
return val != -1;
}
internal byte ReadByte()
{
return baseStream.PeekByteFromBuffer(Position++);
}
internal int ReadInt16()
{
int i1 = ReadByte();
int i2 = ReadByte();
return (i1 << 8) + i2;
}
internal int ReadInt24()
{
int i1 = ReadByte();
int i2 = ReadByte();
int i3 = ReadByte();
return (i1 << 16) + (i2 << 8) + i3;
}
internal byte[] ReadBytes(int length)
{
var buffer = new byte[length];
for (int i = 0; i < buffer.Length; i++)
{
buffer[i] = ReadByte();
}
return buffer;
}
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <returns></returns>
Task<bool> ICustomStreamReader.FillBufferAsync(CancellationToken cancellationToken)
{
return baseStream.FillBufferAsync(cancellationToken);
}
/// <summary>
/// Peeks a byte from buffer.
/// </summary>
/// <param name="index">The index.</param>
/// <returns></returns>
byte ICustomStreamReader.PeekByteFromBuffer(int index)
{
return baseStream.PeekByteFromBuffer(index);
}
/// <summary>
/// Peeks bytes asynchronous.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<byte[]> ICustomStreamReader.PeekBytesAsync(int index, int size, CancellationToken cancellationToken)
{
return baseStream.PeekBytesAsync(index, size, cancellationToken);
}
/// <summary>
/// Peeks a byte asynchronous.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> ICustomStreamReader.PeekByteAsync(int index, CancellationToken cancellationToken)
{
return baseStream.PeekByteAsync(index, cancellationToken);
}
/// <summary>
/// Reads a byte from buffer.
/// </summary>
/// <returns></returns>
/// <exception cref="Exception">Buffer is empty</exception>
byte ICustomStreamReader.ReadByteFromBuffer()
{
return ReadByte();
}
int ICustomStreamReader.Read(byte[] buffer, int offset, int count)
{
return baseStream.Read(buffer, offset, count);
}
/// <summary>
/// Reads the asynchronous.
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <param name="offset">The offset.</param>
/// <param name="count">The count.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> ICustomStreamReader.ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return baseStream.ReadAsync(buffer, offset, count, cancellationToken);
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<string> ICustomStreamReader.ReadLineAsync(CancellationToken cancellationToken)
{
return CustomBufferedStream.ReadLineInternalAsync(this, bufferPool, cancellationToken);
}
}
}
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StreamExtended.Network
{
/// <summary>
/// A custom network stream inherited from stream
/// with an underlying read buffer supporting both read/write
/// of UTF-8 encoded string or raw bytes asynchronously from last read position.
/// </summary>
/// <seealso cref="System.IO.Stream" />
public class CustomBufferedStream : Stream, ICustomStreamReader
{
private readonly Stream baseStream;
private readonly bool leaveOpen;
private byte[] streamBuffer;
// default to UTF-8
private static readonly Encoding encoding = Encoding.UTF8;
private int bufferLength;
private int bufferPos;
private bool disposed;
private bool closed;
private readonly IBufferPool bufferPool;
public int BufferSize { get; }
public event EventHandler<DataEventArgs> DataRead;
public event EventHandler<DataEventArgs> DataWrite;
public bool IsClosed => closed;
/// <summary>
/// Initializes a new instance of the <see cref="CustomBufferedStream"/> class.
/// </summary>
/// <param name="baseStream">The base stream.</param>
/// <param name="bufferPool">Bufferpool.</param>
/// <param name="bufferSize">Size of the buffer.</param>
/// <param name="leaveOpen"><see langword="true" /> to leave the stream open after disposing the <see cref="T:CustomBufferedStream" /> object; otherwise, <see langword="false" />.</param>
public CustomBufferedStream(Stream baseStream, IBufferPool bufferPool, int bufferSize, bool leaveOpen = false)
{
this.baseStream = baseStream;
BufferSize = bufferSize;
this.leaveOpen = leaveOpen;
streamBuffer = bufferPool.GetBuffer(bufferSize);
this.bufferPool = bufferPool;
}
/// <summary>
/// When overridden in a derived class, clears all buffers for this stream and causes any buffered data to be written to the underlying device.
/// </summary>
public override void Flush()
{
baseStream.Flush();
}
/// <summary>
/// When overridden in a derived class, sets the position within the current stream.
/// </summary>
/// <param name="offset">A byte offset relative to the <paramref name="origin" /> parameter.</param>
/// <param name="origin">A value of type <see cref="T:System.IO.SeekOrigin" /> indicating the reference point used to obtain the new position.</param>
/// <returns>
/// The new position within the current stream.
/// </returns>
public override long Seek(long offset, SeekOrigin origin)
{
bufferLength = 0;
bufferPos = 0;
return baseStream.Seek(offset, origin);
}
/// <summary>
/// When overridden in a derived class, sets the length of the current stream.
/// </summary>
/// <param name="value">The desired length of the current stream in bytes.</param>
public override void SetLength(long value)
{
baseStream.SetLength(value);
}
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
/// </summary>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
/// </returns>
public override int Read(byte[] buffer, int offset, int count)
{
if (bufferLength == 0)
{
FillBuffer();
}
int available = Math.Min(bufferLength, count);
if (available > 0)
{
Buffer.BlockCopy(streamBuffer, bufferPos, buffer, offset, available);
bufferPos += available;
bufferLength -= available;
}
return available;
}
/// <summary>
/// When overridden in a derived class, writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written.
/// </summary>
/// <param name="buffer">An array of bytes. This method copies <paramref name="count" /> bytes from <paramref name="buffer" /> to the current stream.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin copying bytes to the current stream.</param>
/// <param name="count">The number of bytes to be written to the current stream.</param>
[DebuggerStepThrough]
public override void Write(byte[] buffer, int offset, int count)
{
OnDataWrite(buffer, offset, count);
baseStream.Write(buffer, offset, count);
}
/// <summary>
/// Asynchronously reads the bytes from the current stream and writes them to another stream, using a specified buffer size and cancellation token.
/// </summary>
/// <param name="destination">The stream to which the contents of the current stream will be copied.</param>
/// <param name="bufferSize">The size, in bytes, of the buffer. This value must be greater than zero. The default size is 81920.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous copy operation.
/// </returns>
public override async Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken = default(CancellationToken))
{
if (bufferLength > 0)
{
await destination.WriteAsync(streamBuffer, bufferPos, bufferLength, cancellationToken);
bufferLength = 0;
}
await base.CopyToAsync(destination, bufferSize, cancellationToken);
}
/// <summary>
/// Asynchronously clears all buffers for this stream, causes any buffered data to be written to the underlying device, and monitors cancellation requests.
/// </summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous flush operation.
/// </returns>
public override Task FlushAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return baseStream.FlushAsync(cancellationToken);
}
/// <summary>
/// Asynchronously reads a sequence of bytes from the current stream,
/// advances the position within the stream by the number of bytes read,
/// and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write the data into.</param>
/// <param name="offset">The byte offset in <paramref name="buffer" /> at which
/// to begin writing data from the stream.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.
/// The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous read operation.
/// The value of the parameter contains the total
/// number of bytes read into the buffer.
/// The result value can be less than the number of bytes
/// requested if the number of bytes currently available is
/// less than the requested number, or it can be 0 (zero)
/// if the end of the stream has been reached.
/// </returns>
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default(CancellationToken))
{
if (bufferLength == 0)
{
await FillBufferAsync(cancellationToken);
}
int available = Math.Min(bufferLength, count);
if (available > 0)
{
Buffer.BlockCopy(streamBuffer, bufferPos, buffer, offset, available);
bufferPos += available;
bufferLength -= available;
}
return available;
}
/// <summary>
/// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream.
/// </summary>
/// <returns>
/// The unsigned byte cast to an Int32, or -1 if at the end of the stream.
/// </returns>
public override int ReadByte()
{
if (bufferLength == 0)
{
FillBuffer();
}
if (bufferLength == 0)
{
return -1;
}
bufferLength--;
return streamBuffer[bufferPos++];
}
/// <summary>
/// Peeks a byte asynchronous.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public async Task<int> PeekByteAsync(int index, CancellationToken cancellationToken = default(CancellationToken))
{
if (Available <= index)
{
await FillBufferAsync(cancellationToken);
}
//When index is greater than the buffer size
if (streamBuffer.Length <= index)
{
throw new Exception("Requested Peek index exceeds the buffer size. Consider increasing the buffer size.");
}
//When index is greater than the buffer size
if (Available <= index)
{
return -1;
}
return streamBuffer[bufferPos + index];
}
/// <summary>
/// Peeks bytes asynchronous.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public async Task<byte[]> PeekBytesAsync(int index, int size, CancellationToken cancellationToken = default(CancellationToken))
{
if (Available <= index)
{
await FillBufferAsync(cancellationToken);
}
//When index is greater than the buffer size
if (streamBuffer.Length <= (index + size))
{
throw new Exception("Requested Peek index and size exceeds the buffer size. Consider increasing the buffer size.");
}
if (Available <= (index + size))
{
return null;
}
var vRet = new byte[size];
Array.Copy(streamBuffer, index, vRet, 0, size);
return vRet;
}
/// <summary>
/// Peeks a byte from buffer.
/// </summary>
/// <param name="index">The index.</param>
/// <returns></returns>
/// <exception cref="Exception">Index is out of buffer size</exception>
public byte PeekByteFromBuffer(int index)
{
if (bufferLength <= index)
{
throw new Exception("Index is out of buffer size");
}
return streamBuffer[bufferPos + index];
}
/// <summary>
/// Reads a byte from buffer.
/// </summary>
/// <returns></returns>
/// <exception cref="Exception">Buffer is empty</exception>
public byte ReadByteFromBuffer()
{
if (bufferLength == 0)
{
throw new Exception("Buffer is empty");
}
bufferLength--;
return streamBuffer[bufferPos++];
}
/// <summary>
/// Asynchronously writes a sequence of bytes to the current stream, advances the current position within this stream by the number of bytes written, and monitors cancellation requests.
/// </summary>
/// <param name="buffer">The buffer to write data from.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> from which to begin copying bytes to the stream.</param>
/// <param name="count">The maximum number of bytes to write.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
/// <returns>
/// A task that represents the asynchronous write operation.
/// </returns>
[DebuggerStepThrough]
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default(CancellationToken))
{
OnDataWrite(buffer, offset, count);
await baseStream.WriteAsync(buffer, offset, count, cancellationToken);
}
/// <summary>
/// Writes a byte to the current position in the stream and advances the position within the stream by one byte.
/// </summary>
/// <param name="value">The byte to write to the stream.</param>
public override void WriteByte(byte value)
{
var buffer = bufferPool.GetBuffer(BufferSize);
try
{
buffer[0] = value;
OnDataWrite(buffer, 0, 1);
baseStream.Write(buffer, 0, 1);
}
finally
{
bufferPool.ReturnBuffer(buffer);
}
}
protected virtual void OnDataWrite(byte[] buffer, int offset, int count)
{
DataWrite?.Invoke(this, new DataEventArgs(buffer, offset, count));
}
protected virtual void OnDataRead(byte[] buffer, int offset, int count)
{
DataRead?.Invoke(this, new DataEventArgs(buffer, offset, count));
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.IO.Stream" /> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (!disposed)
{
disposed = true;
closed = true;
if (!leaveOpen)
{
baseStream.Dispose();
}
var buffer = streamBuffer;
streamBuffer = null;
bufferPool.ReturnBuffer(buffer);
}
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports reading.
/// </summary>
public override bool CanRead => baseStream.CanRead;
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports seeking.
/// </summary>
public override bool CanSeek => baseStream.CanSeek;
/// <summary>
/// When overridden in a derived class, gets a value indicating whether the current stream supports writing.
/// </summary>
public override bool CanWrite => baseStream.CanWrite;
/// <summary>
/// Gets a value that determines whether the current stream can time out.
/// </summary>
public override bool CanTimeout => baseStream.CanTimeout;
/// <summary>
/// When overridden in a derived class, gets the length in bytes of the stream.
/// </summary>
public override long Length => baseStream.Length;
/// <summary>
/// Gets a value indicating whether data is available.
/// </summary>
public bool DataAvailable => bufferLength > 0;
/// <summary>
/// Gets the available data size.
/// </summary>
public int Available => bufferLength;
/// <summary>
/// When overridden in a derived class, gets or sets the position within the current stream.
/// </summary>
public override long Position
{
get => baseStream.Position;
set => baseStream.Position = value;
}
/// <summary>
/// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to read before timing out.
/// </summary>
public override int ReadTimeout
{
get => baseStream.ReadTimeout;
set => baseStream.ReadTimeout = value;
}
/// <summary>
/// Gets or sets a value, in miliseconds, that determines how long the stream will attempt to write before timing out.
/// </summary>
public override int WriteTimeout
{
get => baseStream.WriteTimeout;
set => baseStream.WriteTimeout = value;
}
/// <summary>
/// Fills the buffer.
/// </summary>
public bool FillBuffer()
{
if (closed)
{
return false;
}
if (bufferLength > 0)
{
//normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remanining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
bufferPos = 0;
int readBytes = baseStream.Read(streamBuffer, bufferLength, streamBuffer.Length - bufferLength);
bool result = readBytes > 0;
if (result)
{
OnDataRead(streamBuffer, bufferLength, readBytes);
bufferLength += readBytes;
}
else
{
closed = true;
}
return result;
}
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public async Task<bool> FillBufferAsync(CancellationToken cancellationToken = default(CancellationToken))
{
if (closed)
{
return false;
}
if (bufferLength > 0)
{
//normally we fill the buffer only when it is empty, but sometimes we need more data
//move the remanining data to the beginning of the buffer
Buffer.BlockCopy(streamBuffer, bufferPos, streamBuffer, 0, bufferLength);
}
int bytesToRead = streamBuffer.Length - bufferLength;
if (bytesToRead == 0)
{
return false;
}
bufferPos = 0;
int readBytes = await baseStream.ReadAsync(streamBuffer, bufferLength, bytesToRead, cancellationToken);
bool result = readBytes > 0;
if (result)
{
OnDataRead(streamBuffer, bufferLength, readBytes);
bufferLength += readBytes;
}
else
{
closed = true;
}
return result;
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
public Task<string> ReadLineAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return ReadLineInternalAsync(this, bufferPool, cancellationToken);
}
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
internal static async Task<string> ReadLineInternalAsync(ICustomStreamReader reader, IBufferPool bufferPool, CancellationToken cancellationToken = default(CancellationToken))
{
byte lastChar = default(byte);
int bufferDataLength = 0;
// try to use buffer from the buffer pool, usually it is enough
var bufferPoolBuffer = bufferPool.GetBuffer(reader.BufferSize);
var buffer = bufferPoolBuffer;
try
{
while (reader.DataAvailable || await reader.FillBufferAsync(cancellationToken))
{
byte newChar = reader.ReadByteFromBuffer();
buffer[bufferDataLength] = newChar;
//if new line
if (newChar == '\n')
{
if (lastChar == '\r')
{
return encoding.GetString(buffer, 0, bufferDataLength - 1);
}
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);
}
}
}
finally
{
bufferPool.ReturnBuffer(bufferPoolBuffer);
}
if (bufferDataLength == 0)
{
return null;
}
return encoding.GetString(buffer, 0, bufferDataLength);
}
/// <summary>
/// Read until the last new line, ignores the result
/// </summary>
/// <returns></returns>
public async Task ReadAndIgnoreAllLinesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
while (!string.IsNullOrEmpty(await ReadLineAsync(cancellationToken)))
{
}
}
/// <summary>
/// Increase size of buffer and copy existing content to new buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="size"></param>
private static void ResizeBuffer(ref byte[] buffer, long size)
{
var newBuffer = new byte[size];
Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
buffer = newBuffer;
}
#if NET45
/// <summary>
/// Base Stream.BeginRead will call this.Read and block thread (we don't want this, Network stream handles async)
/// In order to really async Reading Launch this.ReadAsync as Task will fire NetworkStream.ReadAsync
/// See Threads here :
/// https://github.com/justcoding121/Stream-Extended/pull/43
/// https://github.com/justcoding121/Titanium-Web-Proxy/issues/575
/// </summary>
/// <returns></returns>
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
var vAsyncResult = this.ReadAsync(buffer, offset, count);
vAsyncResult.ContinueWith(pAsyncResult =>
{
//use TaskExtended to pass State as AsyncObject
//callback will call EndRead (otherwise, it will block)
callback(new TaskResult<int>(pAsyncResult, state));
});
return vAsyncResult;
}
/// <summary>
/// override EndRead to handle async Reading (see BeginRead comment)
/// </summary>
/// <returns></returns>
public override int EndRead(IAsyncResult asyncResult)
{
return ((TaskResult<int>)asyncResult).Result;
}
/// <summary>
/// Fix the .net bug with SslStream slow WriteAsync
/// https://github.com/justcoding121/Titanium-Web-Proxy/issues/495
/// Stream.BeginWrite + Stream.BeginRead uses the same SemaphoreSlim(1)
/// That's why we need to call NetworkStream.BeginWrite only (while read is waiting SemaphoreSlim)
/// </summary>
/// <returns></returns>
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
return baseStream.BeginWrite(buffer, offset, count, callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
baseStream.EndWrite(asyncResult);
}
#endif
}
}
using System;
namespace StreamExtended.Network
{
/// <summary>
/// Wraps the data sent/received event argument.
/// </summary>
public class DataEventArgs : EventArgs
{
public DataEventArgs(byte[] buffer, int offset, int count)
{
Buffer = buffer;
Offset = offset;
Count = count;
}
/// <summary>
/// The buffer with data.
/// </summary>
public byte[] Buffer { get; }
/// <summary>
/// Offset in buffer from which valid data begins.
/// </summary>
public int Offset { get; }
/// <summary>
/// Length from offset in buffer with valid data.
/// </summary>
public int Count { get; }
}
}
using System;
using System.Threading;
using System.Threading.Tasks;
namespace StreamExtended.Network
{
/// <summary>
/// This concrete implemetation of interface acts as the source stream for CopyStream class.
/// </summary>
public interface ICustomStreamReader
{
int BufferSize { get; }
int Available { get; }
bool DataAvailable { get; }
/// <summary>
/// Fills the buffer asynchronous.
/// </summary>
/// <returns></returns>
Task<bool> FillBufferAsync(CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Peeks a byte from buffer.
/// </summary>
/// <param name="index">The index.</param>
/// <returns></returns>
/// <exception cref="Exception">Index is out of buffer size</exception>
byte PeekByteFromBuffer(int index);
/// <summary>
/// Peeks a byte asynchronous.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<int> PeekByteAsync(int index, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Peeks bytes asynchronous.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
Task<byte[]> PeekBytesAsync(int index, int size, CancellationToken cancellationToken = default(CancellationToken));
byte ReadByteFromBuffer();
/// <summary>
/// When overridden in a derived class, reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read.
/// </summary>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between <paramref name="offset" /> and (<paramref name="offset" /> + <paramref name="count" /> - 1) replaced by the bytes read from the current source.</param>
/// <param name="offset">The zero-based byte offset in <paramref name="buffer" /> at which to begin storing the data read from the current stream.</param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns>
/// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached.
/// </returns>
int Read(byte[] buffer, int offset, int count);
/// <summary>
/// Read the specified number (or less) of raw bytes from the base stream to the given buffer to the specified offset
/// </summary>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="bytesToRead"></param>
/// <param name="cancellationToken"></param>
/// <returns>The number of bytes read</returns>
Task<int> ReadAsync(byte[] buffer, int offset, int bytesToRead,
CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Read a line from the byte stream
/// </summary>
/// <returns></returns>
Task<string> ReadLineAsync(CancellationToken cancellationToken = default(CancellationToken));
}
}
\ No newline at end of file
using System.Threading;
using System.Threading.Tasks;
namespace StreamExtended.Network
{
/// <summary>
/// A concrete implementation of this interface is required when calling CopyStream.
/// </summary>
public interface ICustomStreamWriter
{
void Write(byte[] buffer, int i, int bufferLength);
Task WriteAsync(byte[] buffer, int i, int bufferLength, CancellationToken cancellationToken);
}
}
\ No newline at end of file
using System.Diagnostics;
using System.IO;
using System.Threading;
namespace StreamExtended.Network
{
public class ServerHelloAlpnAdderStream : Stream
{
private readonly IBufferPool bufferPool;
private readonly CustomBufferedStream stream;
private bool called;
public ServerHelloAlpnAdderStream(CustomBufferedStream stream, IBufferPool bufferPool)
{
this.bufferPool = bufferPool;
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 cts = new CancellationTokenSource();
var serverHello = SslTools.PeekServerHello(new CustomBufferedStream(ms, bufferPool, (int)ms.Length), bufferPool, cts.Token).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 => stream.Position;
set => stream.Position = value;
}
}
}
\ No newline at end of file
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("StreamExtended.Properties")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("StreamExtended.Properties")]
[assembly: AssemblyCopyright("Copyright © Titanium 2015-2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("5036e0b7-a0d0-4070-8eb0-72c129dee9a3")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.1")]
[assembly: AssemblyFileVersion("1.0.1")]
using StreamExtended.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StreamExtended
{
/// <summary>
/// Wraps up the server SSL hello information.
/// </summary>
public class ServerHelloInfo
{
private static readonly string[] compressions = {
"null",
"DEFLATE"
};
public int HandshakeVersion { get; set; }
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 Dictionary<string, 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 SSLv{HandshakeVersion}-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.Values.OrderBy(x => x.Position))
{
sb.AppendLine($"{extension.Name}: {extension.Data}");
}
}
string compression = compressions.Length > CompressionMethod
? compressions[CompressionMethod]
: $"unknown [0x{CompressionMethod:X2}]";
sb.AppendLine($"Compression: {compression}");
sb.Append("Cipher:");
if (!SslCiphers.Ciphers.TryGetValue(CipherSuite, out string cipherStr))
{
cipherStr = "unknown";
}
sb.AppendLine($"[0x{CipherSuite:X4}] {cipherStr}");
return sb.ToString();
}
}
}
\ No newline at end of file
using StreamExtended.Models;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace StreamExtended
{
internal class SslExtensions
{
internal static SslExtension GetExtension(int value, byte[] data, int position)
{
string name = GetExtensionName(value);
string dataStr = GetExtensionData(value, data);
return new SslExtension(value, name, dataStr, position);
}
private static string GetExtensionData(int value, byte[] data)
{
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value)
{
case 0:
var stringBuilder = new StringBuilder();
int index = 2;
while (index < data.Length)
{
int nameType = data[index];
int count = (data[index + 1] << 8) + data[index + 2];
string str = Encoding.ASCII.GetString(data, index + 3, count);
if (nameType == 0)
{
stringBuilder.AppendFormat("{0}{1}", stringBuilder.Length > 1 ? "; " : string.Empty, str);
}
index += 3 + count;
}
return stringBuilder.ToString();
case 5:
if (data.Length == 5 && data[0] == 1 && data[1] == 0 && data[2] == 0 && data[3] == 0 && data[4] == 0)
{
return "OCSP - Implicit Responder";
}
return ByteArrayToString(data);
case 10:
return GetSupportedGroup(data);
case 11:
return GetEcPointFormats(data);
case 13:
return GetSignatureAlgorithms(data);
case 16:
return GetApplicationLayerProtocolNegotiation(data);
case 35655:
return $"{data.Length} bytes";
default:
return ByteArrayToString(data);
}
}
private static string GetSupportedGroup(byte[] data)
{
//https://datatracker.ietf.org/doc/draft-ietf-tls-rfc4492bis/?include_text=1
List<string> list = new List<string>();
if (data.Length < 2)
{
return string.Empty;
}
int i = 2;
while (i < data.Length - 1)
{
int namedCurve = (data[i] << 8) + data[i + 1];
switch (namedCurve)
{
case 1:
list.Add("sect163k1 [0x1]"); //deprecated
break;
case 2:
list.Add("sect163r1 [0x2]"); //deprecated
break;
case 3:
list.Add("sect163r2 [0x3]"); //deprecated
break;
case 4:
list.Add("sect193r1 [0x4]"); //deprecated
break;
case 5:
list.Add("sect193r2 [0x5]"); //deprecated
break;
case 6:
list.Add("sect233k1 [0x6]"); //deprecated
break;
case 7:
list.Add("sect233r1 [0x7]"); //deprecated
break;
case 8:
list.Add("sect239k1 [0x8]"); //deprecated
break;
case 9:
list.Add("sect283k1 [0x9]"); //deprecated
break;
case 10:
list.Add("sect283r1 [0xA]"); //deprecated
break;
case 11:
list.Add("sect409k1 [0xB]"); //deprecated
break;
case 12:
list.Add("sect409r1 [0xC]"); //deprecated
break;
case 13:
list.Add("sect571k1 [0xD]"); //deprecated
break;
case 14:
list.Add("sect571r1 [0xE]"); //deprecated
break;
case 15:
list.Add("secp160k1 [0xF]"); //deprecated
break;
case 16:
list.Add("secp160r1 [0x10]"); //deprecated
break;
case 17:
list.Add("secp160r2 [0x11]"); //deprecated
break;
case 18:
list.Add("secp192k1 [0x12]"); //deprecated
break;
case 19:
list.Add("secp192r1 [0x13]"); //deprecated
break;
case 20:
list.Add("secp224k1 [0x14]"); //deprecated
break;
case 21:
list.Add("secp224r1 [0x15]"); //deprecated
break;
case 22:
list.Add("secp256k1 [0x16]"); //deprecated
break;
case 23:
list.Add("secp256r1 [0x17]");
break;
case 24:
list.Add("secp384r1 [0x18]");
break;
case 25:
list.Add("secp521r1 [0x19]");
break;
case 26:
list.Add("brainpoolP256r1 [0x1A]");
break;
case 27:
list.Add("brainpoolP384r1 [0x1B]");
break;
case 28:
list.Add("brainpoolP512r1 [0x1C]");
break;
case 29:
list.Add("x25519 [0x1D]");
break;
case 30:
list.Add("x448 [0x1E]");
break;
case 256:
list.Add("ffdhe2048 [0x0100]");
break;
case 257:
list.Add("ffdhe3072 [0x0101]");
break;
case 258:
list.Add("ffdhe4096 [0x0102]");
break;
case 259:
list.Add("ffdhe6144 [0x0103]");
break;
case 260:
list.Add("ffdhe8192 [0x0104]");
break;
case 65281:
list.Add("arbitrary_explicit_prime_curves [0xFF01]"); //deprecated
break;
case 65282:
list.Add("arbitrary_explicit_char2_curves [0xFF02]"); //deprecated
break;
default:
list.Add($"unknown [0x{namedCurve:X4}]");
break;
}
i += 2;
}
return string.Join(", ", list.ToArray());
}
private static string GetEcPointFormats(byte[] data)
{
List<string> list = new List<string>();
if (data.Length < 1)
{
return string.Empty;
}
int i = 1;
while (i < data.Length)
{
switch (data[i])
{
case 0:
list.Add("uncompressed [0x0]");
break;
case 1:
list.Add("ansiX962_compressed_prime [0x1]");
break;
case 2:
list.Add("ansiX962_compressed_char2 [0x2]");
break;
default:
list.Add($"unknown [0x{data[i]:X2}]");
break;
}
i += 2;
}
return string.Join(", ", list.ToArray());
}
private static string GetSignatureAlgorithms(byte[] data)
{
// https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml
int num = (data[0] << 8) + data[1];
var sb = new StringBuilder();
int index = 2;
while (index < num + 2)
{
switch (data[index])
{
case 0:
sb.Append("none");
break;
case 1:
sb.Append("md5");
break;
case 2:
sb.Append("sha1");
break;
case 3:
sb.Append("sha224");
break;
case 4:
sb.Append("sha256");
break;
case 5:
sb.Append("sha384");
break;
case 6:
sb.Append("sha512");
break;
case 8:
sb.Append("Intrinsic");
break;
default:
sb.AppendFormat("Unknown[0x{0:X2}]", data[index]);
break;
}
sb.AppendFormat("_");
switch (data[index + 1])
{
case 0:
sb.Append("anonymous");
break;
case 1:
sb.Append("rsa");
break;
case 2:
sb.Append("dsa");
break;
case 3:
sb.Append("ecdsa");
break;
case 7:
sb.Append("ed25519");
break;
case 8:
sb.Append("ed448");
break;
default:
sb.AppendFormat("Unknown[0x{0:X2}]", data[index + 1]);
break;
}
sb.AppendFormat(", ");
index += 2;
}
if (sb.Length > 1)
sb.Length -= 2;
return sb.ToString();
}
private static string GetApplicationLayerProtocolNegotiation(byte[] data)
{
List<string> stringList = new List<string>();
int index = 2;
while (index < data.Length)
{
int count = data[index];
stringList.Add(Encoding.ASCII.GetString(data, index + 1, count));
index += 1 + count;
}
return string.Join(", ", stringList.ToArray());
}
private static string GetExtensionName(int value)
{
//https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
switch (value)
{
case 0:
return "server_name";
case 1:
return "max_fragment_length";
case 2:
return "client_certificate_url";
case 3:
return "trusted_ca_keys";
case 4:
return "truncated_hmac";
case 5:
return "status_request";
case 6:
return "user_mapping";
case 7:
return "client_authz";
case 8:
return "server_authz";
case 9:
return "cert_type";
case 10:
return "supported_groups"; // renamed from "elliptic_curves" (RFC 7919 / TLS 1.3)
case 11:
return "ec_point_formats";
case 12:
return "srp";
case 13:
return "signature_algorithms";
case 14:
return "use_srtp";
case 15:
return "heartbeat";
case 16:
return "ALPN"; // application_layer_protocol_negotiation
case 17:
return "status_request_v2";
case 18:
return "signed_certificate_timestamp";
case 19:
return "client_certificate_type";
case 20:
return "server_certificate_type";
case 21:
return "padding";
case 22:
return "encrypt_then_mac";
case 23:
return "extended_master_secret";
case 24:
return "token_binding"; // TEMPORARY - registered 2016-02-04, extension registered 2017-01-12, expires 2018-02-04
case 25:
return "cached_info";
case 26:
return "quic_transports_parameters"; // Not yet assigned by IANA (QUIC-TLS Draft04)
case 35:
return "SessionTicket TLS";
// TLS 1.3 draft: https://tools.ietf.org/html/draft-ietf-tls-tls13
case 40:
return "key_share";
case 41:
return "pre_shared_key";
case 42:
return "early_data";
case 43:
return "supported_versions";
case 44:
return "cookie";
case 45:
return "psk_key_exchange_modes";
case 46:
return "ticket_early_data_info";
case 47:
return "certificate_authorities";
case 48:
return "oid_filters";
case 49:
return "post_handshake_auth";
case 2570: // 0a0a
case 6682: // 1a1a
case 10794: // 2a2a
case 14906: // 3a3a
case 19018: // 4a4a
case 23130: // 5a5a
case 27242: // 6a6a
case 31354: // 7a7a
case 35466: // 8a8a
case 39578: // 9a9a
case 43690: // aaaa
case 47802: // baba
case 51914: // caca
case 56026: // dada
case 60138: // eaea
case 64250: // fafa
return "Reserved (GREASE)";
case 13172:
return "next_protocol_negotiation";
case 30031:
return "channel_id_old"; // Google
case 30032:
return "channel_id"; // Google
case 35655:
return "draft-agl-tls-padding";
case 65281:
return "renegotiation_info";
case 65282:
return "Draft version of TLS 1.3"; // for experimentation only https://www.ietf.org/mail-archive/web/tls/current/msg20853.html
default:
return $"unknown_{value:x2}";
}
}
private static string ByteArrayToString(byte[] data)
{
return string.Join(" ", data.Select(x => x.ToString("X2")));
}
}
}
using StreamExtended.Models;
using StreamExtended.Network;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace StreamExtended
{
/// <summary>
/// Use this class to peek SSL client/server hello information.
/// </summary>
public class SslTools
{
/// <summary>
/// Is the given stream starts with an SSL client hello?
/// </summary>
/// <param name="stream"></param>
/// <param name="bufferPool"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<bool> IsClientHello(CustomBufferedStream stream, IBufferPool bufferPool, CancellationToken cancellationToken)
{
var clientHello = await PeekClientHello(stream, bufferPool, cancellationToken);
return clientHello != null;
}
/// <summary>
/// Peek the SSL client hello information.
/// </summary>
/// <param name="clientStream"></param>
/// <param name="bufferPool"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<ClientHelloInfo> PeekClientHello(CustomBufferedStream clientStream, IBufferPool bufferPool, CancellationToken cancellationToken = default (CancellationToken))
{
//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, cancellationToken);
if (recordType == -1)
{
return null;
}
if ((recordType & 0x80) == 0x80)
{
//SSL 2
var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 1);
// length value + minimum length
if (!await peekStream.EnsureBufferLength(10, cancellationToken))
{
return null;
}
int recordLength = ((recordType & 0x7f) << 8) + peekStream.ReadByte();
if (recordLength < 9)
{
// Message body too short.
return null;
}
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return null;
}
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
int ciphersCount = peekStream.ReadInt16() / 3;
int sessionIdLength = peekStream.ReadInt16();
int randomLength = peekStream.ReadInt16();
if (!await peekStream.EnsureBufferLength(ciphersCount * 3 + sessionIdLength + randomLength, cancellationToken))
{
return null;
}
int[] ciphers = new int[ciphersCount];
for (int i = 0; i < ciphers.Length; i++)
{
ciphers[i] = (peekStream.ReadByte() << 16) + (peekStream.ReadByte() << 8) + peekStream.ReadByte();
}
byte[] sessionId = peekStream.ReadBytes(sessionIdLength);
byte[] random = peekStream.ReadBytes(randomLength);
var clientHelloInfo = new ClientHelloInfo
{
HandshakeVersion = 2,
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
Ciphers = ciphers,
ClientHelloLength = peekStream.Position,
};
return clientHelloInfo;
}
else if (recordType == 0x16)
{
var peekStream = new CustomBufferedPeekStream(clientStream, bufferPool, 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, cancellationToken))
{
return null;
}
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
int recordLength = peekStream.ReadInt16();
if (peekStream.ReadByte() != 0x01)
{
// should be ClientHello
return null;
}
var 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, cancellationToken))
{
return null;
}
byte[] sessionId = peekStream.ReadBytes(length);
length = peekStream.ReadInt16();
// ciphersData + compressionData length
if (!await peekStream.EnsureBufferLength(length + 1, cancellationToken))
{
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, cancellationToken))
{
return null;
}
byte[] compressionData = peekStream.ReadBytes(length);
int extenstionsStartPosition = peekStream.Position;
Dictionary<string, SslExtension> extensions = null;
if(extenstionsStartPosition < recordLength + 5)
{
extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, bufferPool, cancellationToken);
}
var clientHelloInfo = new ClientHelloInfo
{
HandshakeVersion = 3,
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
Ciphers = ciphers,
CompressionData = compressionData,
ClientHelloLength = peekStream.Position,
EntensionsStartPosition = extenstionsStartPosition,
Extensions = extensions,
};
return clientHelloInfo;
}
return null;
}
/// <summary>
/// Is the given stream starts with an SSL client hello?
/// </summary>
/// <param name="stream"></param>
/// <param name="bufferPool"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<bool> IsServerHello(CustomBufferedStream stream, IBufferPool bufferPool, CancellationToken cancellationToken)
{
var serverHello = await PeekServerHello(stream, bufferPool, cancellationToken);
return serverHello != null;
}
/// <summary>
/// Peek the SSL client hello information.
/// </summary>
/// <param name="serverStream"></param>
/// <param name="bufferPool"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<ServerHelloInfo> PeekServerHello(CustomBufferedStream serverStream, IBufferPool bufferPool, CancellationToken cancellationToken = default(CancellationToken))
{
//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, cancellationToken);
if (recordType == -1)
{
return null;
}
if ((recordType & 0x80) == 0x80)
{
//SSL 2
// not tested. SSL2 is deprecated
var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 1);
// length value + minimum length
if (!await peekStream.EnsureBufferLength(39, cancellationToken))
{
return null;
}
int recordLength = ((recordType & 0x7f) << 8) + peekStream.ReadByte();
if (recordLength < 38)
{
// Message body too short.
return null;
}
if (peekStream.ReadByte() != 0x04)
{
// should be ServerHello
return null;
}
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
// 32 bytes random + 1 byte sessionId + 2 bytes cipherSuite
if (!await peekStream.EnsureBufferLength(35, cancellationToken))
{
return null;
}
byte[] random = peekStream.ReadBytes(32);
byte[] sessionId = peekStream.ReadBytes(1);
int cipherSuite = peekStream.ReadInt16();
var serverHelloInfo = new ServerHelloInfo
{
HandshakeVersion = 2,
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
CipherSuite = cipherSuite,
ServerHelloLength = peekStream.Position,
};
return serverHelloInfo;
}
else if (recordType == 0x16)
{
var peekStream = new CustomBufferedPeekStream(serverStream, bufferPool, 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, cancellationToken))
{
return null;
}
//SSL 3.0 or TLS 1.0, 1.1 and 1.2
int majorVersion = peekStream.ReadByte();
int minorVersion = peekStream.ReadByte();
int recordLength = peekStream.ReadInt16();
if (peekStream.ReadByte() != 0x02)
{
// should be ServerHello
return null;
}
var 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, cancellationToken))
{
return null;
}
byte[] sessionId = peekStream.ReadBytes(length);
int cipherSuite = peekStream.ReadInt16();
byte compressionMethod = peekStream.ReadByte();
int extenstionsStartPosition = peekStream.Position;
Dictionary<string, SslExtension> extensions = null;
if (extenstionsStartPosition < recordLength + 5)
{
extensions = await ReadExtensions(majorVersion, minorVersion, peekStream, bufferPool, cancellationToken);
}
var serverHelloInfo = new ServerHelloInfo
{
HandshakeVersion = 3,
MajorVersion = majorVersion,
MinorVersion = minorVersion,
Random = random,
SessionId = sessionId,
CipherSuite = cipherSuite,
CompressionMethod = compressionMethod,
ServerHelloLength = peekStream.Position,
EntensionsStartPosition = extenstionsStartPosition,
Extensions = extensions,
};
return serverHelloInfo;
}
return null;
}
private static async Task<Dictionary<string, SslExtension>> ReadExtensions(int majorVersion, int minorVersion, CustomBufferedPeekStream peekStream, IBufferPool bufferPool, CancellationToken cancellationToken)
{
Dictionary<string, SslExtension> extensions = null;
if (majorVersion > 3 || majorVersion == 3 && minorVersion >= 1)
{
if (await peekStream.EnsureBufferLength(2, cancellationToken))
{
int extensionsLength = peekStream.ReadInt16();
if (await peekStream.EnsureBufferLength(extensionsLength, cancellationToken))
{
extensions = new Dictionary<string, SslExtension>();
int idx = 0;
while (extensionsLength > 3)
{
int id = peekStream.ReadInt16();
int length = peekStream.ReadInt16();
byte[] data = peekStream.ReadBytes(length);
var extension = SslExtensions.GetExtension(id, data, idx++);
extensions[extension.Name] = extension;
extensionsLength -= 4 + length;
}
}
}
}
return extensions;
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net45;netstandard1.3</TargetFrameworks>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>StrongKey.snk</AssemblyOriginatorKeyFile>
<Version>1.0.0</Version>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<Description>Package Description</Description>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Network\ClientHelloAlpnAdderStream.cs" />
<Compile Remove="Network\ServerHelloAlpnAdderStream.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="StreamExtended.nuspec" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.3.1" />
</ItemGroup>
</Project>
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace StreamExtended
{
/// <summary>
/// Mimic a Task<T> but you can set AsyncState
/// </summary>
/// <typeparam name="T"></typeparam>
public class TaskResult<T> : IAsyncResult
{
Task<T> Task;
object mAsyncState;
public TaskResult(Task<T> pTask, object state)
{
Task = pTask;
mAsyncState = state;
}
public object AsyncState => mAsyncState;
public WaitHandle AsyncWaitHandle => ((IAsyncResult)Task).AsyncWaitHandle;
public bool CompletedSynchronously => ((IAsyncResult)Task).CompletedSynchronously;
public bool IsCompleted => Task.IsCompleted;
public T Result => Task.Result;
}
}
......@@ -23,7 +23,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BC1E0789
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy", "Titanium.Web.Proxy\Titanium.Web.Proxy.csproj", "{91018B6D-A7A9-45BE-9CB3-79CBB8B169A6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.UnitTests", "..\tests\Titanium.Web.Proxy.UnitTests\Titanium.Web.Proxy.UnitTests.csproj", "{B517E3D0-D03B-436F-AB03-34BA0D5321AF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy.UnitTests", "..\tests\Titanium.Web.Proxy.UnitTests\Titanium.Web.Proxy.UnitTests.csproj", "{B517E3D0-D03B-436F-AB03-34BA0D5321AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy.Examples.Basic", "..\examples\Titanium.Web.Proxy.Examples.Basic\Titanium.Web.Proxy.Examples.Basic.csproj", "{1FAC4205-4445-4F2B-BB8F-618E8A0C15FD}"
EndProject
......@@ -31,6 +31,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Titanium.Web.Proxy.Examples
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Titanium.Web.Proxy.IntegrationTests", "..\tests\Titanium.Web.Proxy.IntegrationTests\Titanium.Web.Proxy.IntegrationTests.csproj", "{1D053D72-DCB4-4517-ACDD-D35ADC186950}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StreamExtended", "StreamExtended\StreamExtended.csproj", "{61539DC1-CE80-41E6-A696-8F455ACE8D15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -79,6 +81,14 @@ Global
{1D053D72-DCB4-4517-ACDD-D35ADC186950}.Release|Any CPU.Build.0 = Release|Any CPU
{1D053D72-DCB4-4517-ACDD-D35ADC186950}.Release|x64.ActiveCfg = Release|Any CPU
{1D053D72-DCB4-4517-ACDD-D35ADC186950}.Release|x64.Build.0 = Release|Any CPU
{61539DC1-CE80-41E6-A696-8F455ACE8D15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{61539DC1-CE80-41E6-A696-8F455ACE8D15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61539DC1-CE80-41E6-A696-8F455ACE8D15}.Debug|x64.ActiveCfg = Debug|Any CPU
{61539DC1-CE80-41E6-A696-8F455ACE8D15}.Debug|x64.Build.0 = Debug|Any CPU
{61539DC1-CE80-41E6-A696-8F455ACE8D15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61539DC1-CE80-41E6-A696-8F455ACE8D15}.Release|Any CPU.Build.0 = Release|Any CPU
{61539DC1-CE80-41E6-A696-8F455ACE8D15}.Release|x64.ActiveCfg = Release|Any CPU
{61539DC1-CE80-41E6-A696-8F455ACE8D15}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -90,7 +100,7 @@ Global
{1D053D72-DCB4-4517-ACDD-D35ADC186950} = {BC1E0789-D348-49CF-8B67-5E99D50EDF64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {625C1EB5-44CF-47DE-A85A-B4C8C40ED90A}
EnterpriseLibraryConfigurationToolBinariesPath = .1.505.2\lib\NET35
SolutionGuid = {625C1EB5-44CF-47DE-A85A-B4C8C40ED90A}
EndGlobalSection
EndGlobal
......@@ -17,6 +17,9 @@
<PackageReference Include="StreamExtended" Version="1.0.188-beta" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StreamExtended\StreamExtended.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System.Web" />
......
......@@ -35,6 +35,10 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StreamExtended\StreamExtended.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System.Web" />
</ItemGroup>
......
......@@ -15,7 +15,6 @@
<ItemGroup>
<PackageReference Include="BrotliSharpLib" Version="0.3.3" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.5" />
<PackageReference Include="StreamExtended" Version="1.0.209-beta" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
......@@ -40,6 +39,10 @@
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StreamExtended\StreamExtended.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Network\WinAuth\Security\Common.cs">
<ExcludeFromSourceAnalysis>True</ExcludeFromSourceAnalysis>
......
......@@ -15,19 +15,16 @@
<tags></tags>
<dependencies>
<group targetFramework="net45">
<dependency id="StreamExtended" version="1.0.209-beta" />
<dependency id="Portable.BouncyCastle" version="1.8.5" />
<dependency id="BrotliSharpLib" version="0.3.3" />
</group>
<group targetFramework="netstandard2.0">
<dependency id="StreamExtended" version="1.0.209-beta" />
<dependency id="Portable.BouncyCastle" version="1.8.5" />
<dependency id="BrotliSharpLib" version="0.3.3" />
<dependency id="Microsoft.Win32.Registry" version="4.4.0" />
<dependency id="System.Security.Principal.Windows" version="4.4.1" />
</group>
<group targetFramework="netcoreapp2.1">
<dependency id="StreamExtended" version="1.0.209-beta" />
<dependency id="Portable.BouncyCastle" version="1.8.5" />
<dependency id="BrotliSharpLib" version="0.3.3" />
<dependency id="Microsoft.Win32.Registry" version="4.4.0" />
......
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